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:
88
tools/citrineos-core-main/packages/core/src/dal/index.ts
Normal file
88
tools/citrineos-core-main/packages/core/src/dal/index.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Transaction as SequelizeTransaction } from 'sequelize';
|
||||
|
||||
export { SequelizeTransaction };
|
||||
export * as sequelize from './layers/sequelize/index.js';
|
||||
export * from './interfaces/index.js';
|
||||
export * from 'sequelize-typescript';
|
||||
export type { PaginatedParams } from './layers/sequelize/index.js';
|
||||
export {
|
||||
Authorization,
|
||||
Boot,
|
||||
ChangeConfiguration,
|
||||
ChargingNeeds,
|
||||
ChargingProfile,
|
||||
ChargingSchedule,
|
||||
ChargingStation,
|
||||
ChargingStationSequence,
|
||||
Component,
|
||||
Connector,
|
||||
DefaultSequelizeInstance,
|
||||
Evse,
|
||||
Location,
|
||||
MeterValue,
|
||||
OCPPMessage,
|
||||
Tariff,
|
||||
StartTransaction,
|
||||
StopTransaction,
|
||||
Transaction,
|
||||
Reservation,
|
||||
Subscription,
|
||||
EvseType,
|
||||
Variable,
|
||||
VariableAttribute,
|
||||
VariableCharacteristics,
|
||||
VariableStatus,
|
||||
Certificate,
|
||||
InstalledCertificate,
|
||||
InstallCertificateAttempt,
|
||||
DeleteCertificateAttempt,
|
||||
CountryNameEnumType,
|
||||
TransactionEvent,
|
||||
LocalListAuthorization,
|
||||
LocalListVersion,
|
||||
SendLocalList,
|
||||
ServerNetworkProfile,
|
||||
SetNetworkProfile,
|
||||
StatusNotification,
|
||||
ChargingStationSecurityInfo,
|
||||
ChargingStationNetworkProfile,
|
||||
Tenant,
|
||||
TenantPartner,
|
||||
AsyncJobStatus,
|
||||
AsyncJobStatusDTO,
|
||||
AsyncJobRequest,
|
||||
SignatureAlgorithmEnumType,
|
||||
SequelizeAuthorizationRepository,
|
||||
SequelizeBootRepository,
|
||||
SequelizeOCPPMessageRepository,
|
||||
SequelizeCertificateRepository,
|
||||
SequelizeInstalledCertificateRepository,
|
||||
SequelizeInstallCertificateAttemptRepository,
|
||||
SequelizeDeleteCertificateAttemptRepository,
|
||||
SequelizeChangeConfigurationRepository,
|
||||
SequelizeChargingProfileRepository,
|
||||
SequelizeChargingStationSecurityInfoRepository,
|
||||
SequelizeDeviceModelRepository,
|
||||
SequelizeLocationRepository,
|
||||
SequelizeMessageInfoRepository,
|
||||
SequelizeRepository,
|
||||
SequelizeReservationRepository,
|
||||
SequelizeSecurityEventRepository,
|
||||
SequelizeSubscriptionRepository,
|
||||
SequelizeTariffRepository,
|
||||
SequelizeTransactionEventRepository,
|
||||
SequelizeVariableMonitoringRepository,
|
||||
SequelizeChargingStationSequenceRepository,
|
||||
SequelizeTenantRepository,
|
||||
SequelizeAsyncJobStatusRepository,
|
||||
SequelizeServerNetworkProfileRepository,
|
||||
OCPP2_0_1_Mapper,
|
||||
OCPP1_6_Mapper,
|
||||
} from './layers/sequelize/index.js'; // TODO ensure all needed modules are properly exported
|
||||
export { RepositoryStore } from './layers/sequelize/repository/RepositoryStore.js';
|
||||
export { DefaultDrizzleInstance } from './layers/drizzle/index.js';
|
||||
export { CryptoUtils } from './util/CryptoUtils.js';
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
CountryNameEnumType,
|
||||
SignatureAlgorithmEnumType,
|
||||
} from '../../layers/sequelize/model/Certificate/index.js';
|
||||
|
||||
export class GenerateCertificateChainRequest {
|
||||
// Fields for generating a certificate
|
||||
// Refer to 1.4.1. Certificate Properties in OCPP 2.0.1 Part 2
|
||||
selfSigned: boolean;
|
||||
organizationName: string;
|
||||
commonName: string;
|
||||
keyLength?: number;
|
||||
validBefore?: string;
|
||||
countryName?: CountryNameEnumType;
|
||||
signatureAlgorithm?: SignatureAlgorithmEnumType;
|
||||
pathLen?: number;
|
||||
// The file path to store the generated certificate.
|
||||
filePath?: string;
|
||||
|
||||
constructor(
|
||||
selfSigned: boolean,
|
||||
organizationName: string,
|
||||
commonName: string,
|
||||
keyLength?: number,
|
||||
validBefore?: string,
|
||||
countryName?: CountryNameEnumType,
|
||||
signatureAlgorithm?: SignatureAlgorithmEnumType,
|
||||
pathLen?: number,
|
||||
filePath?: string,
|
||||
) {
|
||||
this.selfSigned = selfSigned;
|
||||
this.organizationName = organizationName;
|
||||
this.commonName = commonName;
|
||||
this.keyLength = keyLength;
|
||||
this.validBefore = validBefore;
|
||||
this.countryName = countryName;
|
||||
this.signatureAlgorithm = signatureAlgorithm;
|
||||
this.pathLen = pathLen;
|
||||
this.filePath = filePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { OCPP2_0_1 } from '@citrineos/base';
|
||||
|
||||
export class InstallRootCertificateRequest {
|
||||
// Fields for InstallCertificate message request
|
||||
ocppConnectionName: string;
|
||||
certificateType: OCPP2_0_1.InstallCertificateUseEnumType;
|
||||
tenantId: number;
|
||||
callbackUrl?: string;
|
||||
// The file id of the root CA certificate. If not provided, it uses one from the external CA Server
|
||||
// according to the certificate type, e.g., lets encrypt, hubject.
|
||||
fileId?: string;
|
||||
|
||||
constructor(
|
||||
ocppConnectionName: string,
|
||||
tenantId: number,
|
||||
certificateType: OCPP2_0_1.InstallCertificateUseEnumType,
|
||||
callbackUrl?: string,
|
||||
fileId?: string,
|
||||
) {
|
||||
this.ocppConnectionName = ocppConnectionName;
|
||||
this.tenantId = tenantId;
|
||||
this.certificateType = certificateType;
|
||||
this.callbackUrl = callbackUrl;
|
||||
this.fileId = fileId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export class RegenerateExistingCertificate {
|
||||
installedCertificateId!: number;
|
||||
validBefore?: string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { OCPP2_0_1 } from '@citrineos/base';
|
||||
|
||||
export class UploadExistingCertificate {
|
||||
certificate!: string;
|
||||
certificateType!: OCPP2_0_1.GetCertificateIdUseEnumType;
|
||||
filePath?: string;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './repositories.js';
|
||||
|
||||
// Data endpoints query models
|
||||
export { AuthorizationQuerySchema } from './queries/Authorization.js';
|
||||
export type { AuthorizationQuerystring } from './queries/Authorization.js';
|
||||
export { ChargingStationKeyQuerySchema } from './queries/ChargingStation.js';
|
||||
export type { ChargingStationKeyQuerystring } from './queries/ChargingStation.js';
|
||||
export { ConnectionDeleteQuerySchema } from './queries/Connection.js';
|
||||
export type { ConnectionDeleteQuerystring } from './queries/Connection.js';
|
||||
export { ModelKeyQuerystringSchema } from './queries/Model.js';
|
||||
export type { ModelKeyQuerystring } from './queries/Model.js';
|
||||
export {
|
||||
NetworkProfileDeleteQuerySchema,
|
||||
NetworkProfileQuerySchema,
|
||||
} from './queries/NetworkProfile.js';
|
||||
export type {
|
||||
NetworkProfileDeleteQuerystring,
|
||||
NetworkProfileQuerystring,
|
||||
} from './queries/NetworkProfile.js';
|
||||
export {
|
||||
GenerateCertificateChainSchema,
|
||||
InstallRootCertificateSchema,
|
||||
RegenerateInstalledCertificateSchema,
|
||||
UploadExistingCertificateSchema,
|
||||
} from './queries/RootCertificate.js';
|
||||
export { CreateSubscriptionSchema } from './queries/Subscription.js';
|
||||
export { TariffQuerySchema } from './queries/Tariff.js';
|
||||
export type { TariffQueryString } from './queries/Tariff.js';
|
||||
export { CreateTenantQuerySchema, TenantQuerySchema } from './queries/Tenant.js';
|
||||
export type { TenantQueryString } from './queries/Tenant.js';
|
||||
export { TlsReloadQuerySchema } from './queries/TlsReload.js';
|
||||
export type { TlsReloadQueryString } from './queries/TlsReload.js';
|
||||
export { TransactionEventQuerySchema } from './queries/TransactionEvent.js';
|
||||
export type { TransactionEventQuerystring } from './queries/TransactionEvent.js';
|
||||
export { UpdateChargingStationPasswordQuerySchema } from './queries/UpdateChargingStationPasswordQuery.js';
|
||||
export type { UpdateChargingStationPasswordQueryString } from './queries/UpdateChargingStationPasswordQuery.js';
|
||||
export {
|
||||
CreateOrUpdateVariableAttributeQuerySchema,
|
||||
VariableAttributeQuerySchema,
|
||||
} from './queries/VariableAttribute.js';
|
||||
export type {
|
||||
CreateOrUpdateVariableAttributeQuerystring,
|
||||
VariableAttributeQuerystring,
|
||||
} from './queries/VariableAttribute.js';
|
||||
export {
|
||||
WebsocketDeleteQuerySchema,
|
||||
WebsocketGetQuerySchema,
|
||||
WebsocketMappingQuerySchema,
|
||||
WebsocketRequestSchema,
|
||||
} from './queries/Websocket.js';
|
||||
export type {
|
||||
WebsocketDeleteQuerystring,
|
||||
WebsocketGetQuerystring,
|
||||
WebsocketMappingQuerystring,
|
||||
} from './queries/Websocket.js';
|
||||
|
||||
// Data projection models
|
||||
export type { AuthorizationRestrictions } from './projections/AuthorizationRestrictions.js';
|
||||
export { default as AuthorizationRestrictionsSchema } from './projections/schemas/AuthorizationRestrictionsSchema.json' with { type: 'json' };
|
||||
export { default as TariffSchema } from './projections/schemas/TariffSchema.json' with { type: 'json' };
|
||||
|
||||
// Date endpoints DTOs
|
||||
export { GenerateCertificateChainRequest } from './dtos/GenerateCertificateChainRequest.js';
|
||||
export { InstallRootCertificateRequest } from './dtos/InstallRootCertificateRequest.js';
|
||||
export { RegenerateExistingCertificate } from './dtos/RegenerateExistingCertificate.js';
|
||||
export { UploadExistingCertificate } from './dtos/UploadExistingCertificate.js';
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export interface AuthorizationRestrictions {
|
||||
/**
|
||||
* If present, connector types this authorization profile is permitted to charge at.
|
||||
* SHALL use options in {@link ConnectorEnumType} if applicable, plus "cGBT, cChaoJi,
|
||||
* OppCharge" as mentioned in information model, or a custom option if nothing else
|
||||
* fits.
|
||||
*/
|
||||
allowedConnectorTypes?: string[];
|
||||
|
||||
/**
|
||||
* If present, this list will be used to prevent charging at evses which match one of
|
||||
* its strings. EvseId is as defined in Part 2 - Appendices of OCPP 2.0.1, which
|
||||
* references the ISO 15118/IEC 63119-2 format. Strings in this list are treated as
|
||||
* prefixes for matching purposes to allow hierarchical id semantics to exclude entire
|
||||
* stations with one entry, i.e. "US\*A23\*E00235" will match "US\*A23\*E00235\*1" and
|
||||
* "US\*A23\*E00235\*2", which could represent Evse 1 and 2 at the same station.
|
||||
*/
|
||||
disallowedEvseIdPrefixes?: string[];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$id": "AuthorizationRestrictionsSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allowedConnectorTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"disallowedEvseIdPrefixes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"$id": "TariffSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 3
|
||||
},
|
||||
"pricePerKwh": {
|
||||
"type": "number"
|
||||
},
|
||||
"pricePerMin": {
|
||||
"type": "number"
|
||||
},
|
||||
"pricePerSession": {
|
||||
"type": "number"
|
||||
},
|
||||
"paymentFee": {
|
||||
"type": "number"
|
||||
},
|
||||
"authorizationAmount": {
|
||||
"type": "number"
|
||||
},
|
||||
"taxRate": {
|
||||
"type": "number"
|
||||
},
|
||||
"tariffAltText": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"tariffId": {
|
||||
"type": "string"
|
||||
},
|
||||
"validFrom": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"description": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"energy": {
|
||||
"type": "object"
|
||||
},
|
||||
"chargingTime": {
|
||||
"type": "object"
|
||||
},
|
||||
"idleTime": {
|
||||
"type": "object"
|
||||
},
|
||||
"fixedFee": {
|
||||
"type": "object"
|
||||
},
|
||||
"reservationTime": {
|
||||
"type": "object"
|
||||
},
|
||||
"reservationFixed": {
|
||||
"type": "object"
|
||||
},
|
||||
"minCost": {
|
||||
"type": "object"
|
||||
},
|
||||
"maxCost": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["currency", "pricePerKwh"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { QuerySchema } from '@citrineos/base';
|
||||
|
||||
export interface AuthorizationQuerystring {
|
||||
idToken: string;
|
||||
type?: string | null | undefined;
|
||||
}
|
||||
|
||||
export const AuthorizationQuerySchema = QuerySchema('AuthorizationQuerySchema', [
|
||||
{
|
||||
key: 'idToken',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export interface ChargingStationKeyQuerystring {
|
||||
ocppConnectionName: string;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const ChargingStationKeyQuerySchema = QuerySchema('ChargingStationKeyQuerySchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { QuerySchema } from '@citrineos/base';
|
||||
|
||||
export interface ConnectionDeleteQuerystring {
|
||||
ocppConnectionName: string;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const ConnectionDeleteQuerySchema = QuerySchema('ConnectionDeleteQuerySchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { QuerySchema, DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
|
||||
export interface ModelKeyQuerystring {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const ModelKeyQuerystringSchema = QuerySchema('ModelKeyQuerystringSchema', [
|
||||
{
|
||||
key: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export interface NetworkProfileQuerystring {
|
||||
ocppConnectionName: string;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const NetworkProfileQuerySchema = QuerySchema('NetworkProfileQuerySchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
]);
|
||||
|
||||
export interface NetworkProfileDeleteQuerystring {
|
||||
ocppConnectionName: string;
|
||||
configurationSlot: number[];
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const NetworkProfileDeleteQuerySchema = QuerySchema('NetworkProfileDeleteQuerySchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'configurationSlot',
|
||||
type: 'number[]',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export const GenerateCertificateChainSchema = QuerySchema('GenerateCertificateChainSchema', [
|
||||
{
|
||||
key: 'commonName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'organizationName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'selfSigned',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'countryName',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'filePath',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'keyLength',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'pathLen',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'signatureAlgorithm',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'validBefore',
|
||||
type: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
export const InstallRootCertificateSchema = QuerySchema('InstallRootCertificateSchema', [
|
||||
{
|
||||
key: 'certificateType',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
{
|
||||
key: 'callbackUrl',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'fileId',
|
||||
type: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
export const UploadExistingCertificateSchema = QuerySchema('UploadExistingCertificateSchema', [
|
||||
{
|
||||
key: 'certificate',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'certificateType',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'filePath',
|
||||
type: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
export const RegenerateInstalledCertificateSchema = QuerySchema(
|
||||
'RegenerateInstalledCertificateSchema',
|
||||
[
|
||||
{
|
||||
key: 'installedCertificateId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'validBefore',
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { QuerySchema } from '@citrineos/base';
|
||||
|
||||
export const CreateSubscriptionSchema = QuerySchema('CreateSubscriptionSchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'messageRegexFilter',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'onClose',
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
key: 'onConnect',
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
key: 'onMessage',
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
key: 'sentMessage',
|
||||
type: 'boolean',
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export const TariffQuerySchema = QuerySchema('TariffQuerySchema', [
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
type: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
export interface TariffQueryString {
|
||||
tenantId: number;
|
||||
id?: string;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export const TenantQuerySchema = QuerySchema('TenantQuerySchema', [
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
]);
|
||||
|
||||
export interface TenantQueryString {
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const CreateTenantQuerySchema = QuerySchema('CreateTenantQuerySchema', [
|
||||
{
|
||||
key: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'isUserTenant',
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
key: 'maxChargingStations',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'websocketServerConfig',
|
||||
type: 'object',
|
||||
},
|
||||
{
|
||||
key: 'websocketServerId',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'tenantPath',
|
||||
type: 'string',
|
||||
pattern: '^[a-zA-Z0-9_-]+$',
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { QuerySchema } from '@citrineos/base';
|
||||
|
||||
export const TlsReloadQuerySchema = QuerySchema('TlsReloadQuerySchema', [
|
||||
{
|
||||
key: 'serverId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
]);
|
||||
|
||||
export interface TlsReloadQueryString {
|
||||
serverId: string;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export interface TransactionEventQuerystring {
|
||||
ocppConnectionName: string;
|
||||
transactionId: string;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const TransactionEventQuerySchema = QuerySchema('TransactionEventQuerySchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'transactionId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, QuerySchema } from '@citrineos/base';
|
||||
|
||||
export const UpdateChargingStationPasswordQuerySchema = QuerySchema(
|
||||
'UpdateChargingStationPasswordQuerySchema',
|
||||
[
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
{
|
||||
key: 'callbackUrl',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
export interface UpdateChargingStationPasswordQueryString {
|
||||
tenantId: number;
|
||||
callbackUrl?: string;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
QuerySchema,
|
||||
type AttributeEnumType,
|
||||
type SetVariableStatusEnumType,
|
||||
} from '@citrineos/base';
|
||||
|
||||
export interface VariableAttributeQuerystring {
|
||||
ocppConnectionName: string;
|
||||
tenantId: number;
|
||||
type?: AttributeEnumType;
|
||||
value?: string;
|
||||
status?: SetVariableStatusEnumType;
|
||||
component_evse_id?: number;
|
||||
component_evse_connectorId?: number | null;
|
||||
component_name?: string;
|
||||
component_instance?: string | null;
|
||||
variable_name?: string;
|
||||
variable_instance?: string | null;
|
||||
}
|
||||
|
||||
export const VariableAttributeQuerySchema = QuerySchema('VariableAttributeQuerySchema', [
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'component_evse_id',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'component_evse_connectorId',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
key: 'component_name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'component_instance',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'variable_name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'variable_instance',
|
||||
type: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
export interface CreateOrUpdateVariableAttributeQuerystring {
|
||||
tenantId: number;
|
||||
ocppConnectionName: string;
|
||||
setOnCharger?: boolean; // Used to indicate value has already been accepted by the station via means other than ocpp
|
||||
}
|
||||
|
||||
export const CreateOrUpdateVariableAttributeQuerySchema = QuerySchema(
|
||||
'CreateOrUpdateVariableAttributeQuerySchema',
|
||||
[
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
defaultValue: String(DEFAULT_TENANT_ID),
|
||||
},
|
||||
{
|
||||
key: 'ocppConnectionName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'setOnCharger',
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { QuerySchema } from '@citrineos/base';
|
||||
|
||||
export interface WebsocketGetQuerystring {
|
||||
id?: string;
|
||||
tenantId?: number;
|
||||
}
|
||||
|
||||
export const WebsocketGetQuerySchema = QuerySchema('WebsocketGetQuerySchema', [
|
||||
{
|
||||
key: 'id',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'string',
|
||||
},
|
||||
]);
|
||||
|
||||
export interface WebsocketDeleteQuerystring {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const WebsocketDeleteQuerySchema = QuerySchema('WebsocketDeleteQuerySchema', [
|
||||
{
|
||||
key: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
]);
|
||||
|
||||
export const WebsocketRequestSchema = QuerySchema('WebsocketRequestSchema', [
|
||||
{
|
||||
key: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'host',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'port',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'pingInterval',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'protocol',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'securityProfile',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'allowUnknownChargingStations',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tlsKeyFilePath',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'tlsCertificateChainFilePath',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'mtlsCertificateAuthorityKeyFilePath',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'rootCACertificateFilePath',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantPathMapping',
|
||||
type: 'object',
|
||||
},
|
||||
{
|
||||
key: 'dynamicTenantResolution',
|
||||
type: 'boolean',
|
||||
},
|
||||
]);
|
||||
|
||||
export interface WebsocketMappingQuerystring {
|
||||
id: string;
|
||||
path: string;
|
||||
tenantId: number;
|
||||
}
|
||||
|
||||
export const WebsocketMappingQuerySchema = QuerySchema('WebsocketMappingQuerySchema', [
|
||||
{
|
||||
key: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'tenantId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,546 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type {
|
||||
BootConfig,
|
||||
CallAction,
|
||||
ChargingLimitSourceEnumType,
|
||||
ChargingProfilePurposeEnumType,
|
||||
ChargingStateEnumType,
|
||||
ChargingStationSequenceTypeEnumType,
|
||||
CrudRepository,
|
||||
MeterValueDto,
|
||||
OCPP1_6,
|
||||
OCPP2_common_types,
|
||||
OCPP2_request_types,
|
||||
OCPPMessageDto,
|
||||
OCPPVersion,
|
||||
RegistrationStatusEnumType,
|
||||
SecurityEventDto,
|
||||
UpdateEnumType,
|
||||
} from '@citrineos/base';
|
||||
import type {
|
||||
ChargingProfileInput,
|
||||
CompositeScheduleInput,
|
||||
} from '../layers/sequelize/mapper/2.0.1/ChargingProfileMapper.js';
|
||||
import type { Authorization } from '../layers/sequelize/model/Authorization/Authorization.js';
|
||||
import type { LocalListVersion } from '../layers/sequelize/model/Authorization/LocalListVersion.js';
|
||||
import type { SendLocalList } from '../layers/sequelize/model/Authorization/SendLocalList.js';
|
||||
import type { Boot } from '../layers/sequelize/model/Boot.js';
|
||||
import type { Certificate } from '../layers/sequelize/model/Certificate/Certificate.js';
|
||||
import type {
|
||||
DeleteCertificateAttempt,
|
||||
InstallCertificateAttempt,
|
||||
InstalledCertificate,
|
||||
} from '../layers/sequelize/model/Certificate/index.js';
|
||||
import type { ChangeConfiguration } from '../layers/sequelize/model/ChangeConfiguration.js';
|
||||
import type {
|
||||
ChargingNeeds,
|
||||
ChargingProfile,
|
||||
CompositeSchedule,
|
||||
} from '../layers/sequelize/model/ChargingProfile/index.js';
|
||||
import type { ChargingStationSecurityInfo } from '../layers/sequelize/model/ChargingStationSecurityInfo.js';
|
||||
import type { ChargingStationSequence } from '../layers/sequelize/model/ChargingStationSequence/ChargingStationSequence.js';
|
||||
import type { Component } from '../layers/sequelize/model/DeviceModel/Component.js';
|
||||
import type { EvseType } from '../layers/sequelize/model/DeviceModel/EvseType.js';
|
||||
import type { Variable } from '../layers/sequelize/model/DeviceModel/Variable.js';
|
||||
import type { VariableAttribute } from '../layers/sequelize/model/DeviceModel/VariableAttribute.js';
|
||||
import type { VariableCharacteristics } from '../layers/sequelize/model/DeviceModel/VariableCharacteristics.js';
|
||||
import type { ChargingStation } from '../layers/sequelize/model/Location/ChargingStation.js';
|
||||
import type { Connector } from '../layers/sequelize/model/Location/Connector.js';
|
||||
import type { Evse } from '../layers/sequelize/model/Location/Evse.js';
|
||||
import type { Location } from '../layers/sequelize/model/Location/Location.js';
|
||||
import type { ServerNetworkProfile } from '../layers/sequelize/model/Location/ServerNetworkProfile.js';
|
||||
import type { StatusNotification } from '../layers/sequelize/model/Location/StatusNotification.js';
|
||||
import type { MessageInfo } from '../layers/sequelize/model/MessageInfo/MessageInfo.js';
|
||||
import type { OCPPMessage } from '../layers/sequelize/model/OCPPMessage.js';
|
||||
import type { Reservation } from '../layers/sequelize/model/Reservation.js';
|
||||
import type { Subscription } from '../layers/sequelize/model/Subscription/Subscription.js';
|
||||
import type { Tariff } from '../layers/sequelize/model/Tariff/Tariffs.js';
|
||||
import type { Tenant } from '../layers/sequelize/model/Tenant.js';
|
||||
import type {
|
||||
MeterValue,
|
||||
StopTransaction,
|
||||
Transaction,
|
||||
} from '../layers/sequelize/model/TransactionEvent/index.js';
|
||||
import type { TransactionEvent } from '../layers/sequelize/model/TransactionEvent/TransactionEvent.js';
|
||||
import type {
|
||||
EventData,
|
||||
VariableMonitoring,
|
||||
} from '../layers/sequelize/model/VariableMonitoring/index.js';
|
||||
import type { AuthorizationQuerystring } from './queries/Authorization.js';
|
||||
import type { TariffQueryString } from './queries/Tariff.js';
|
||||
import type { VariableAttributeQuerystring } from './queries/VariableAttribute.js';
|
||||
|
||||
export interface IAuthorizationRepository extends CrudRepository<Authorization> {
|
||||
readAllByQuerystring: (
|
||||
tenantId: number,
|
||||
query: AuthorizationQuerystring,
|
||||
) => Promise<Authorization[]>;
|
||||
readOnlyOneByQuerystring: (
|
||||
tenantId: number,
|
||||
query: AuthorizationQuerystring,
|
||||
) => Promise<Authorization | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key is StationId
|
||||
*/
|
||||
export interface IBootRepository extends CrudRepository<BootConfig> {
|
||||
createOrUpdateByKey: (
|
||||
tenantId: number,
|
||||
value: BootConfig,
|
||||
key: string,
|
||||
) => Promise<Boot | undefined>;
|
||||
updateStatusByKey: (
|
||||
tenantId: number,
|
||||
status: RegistrationStatusEnumType,
|
||||
statusInfo: OCPP2_common_types.StatusInfoType | undefined,
|
||||
key: string,
|
||||
) => Promise<Boot | undefined>;
|
||||
updateLastBootTimeByKey: (
|
||||
tenantId: number,
|
||||
lastBootTime: string,
|
||||
key: string,
|
||||
) => Promise<Boot | undefined>;
|
||||
readByKey: (tenantId: number, key: string) => Promise<Boot | undefined>;
|
||||
existsByKey: (tenantId: number, key: string) => Promise<boolean>;
|
||||
deleteByKey: (tenantId: number, key: string) => Promise<Boot | undefined>;
|
||||
}
|
||||
|
||||
export interface IDeviceModelRepository
|
||||
extends CrudRepository<OCPP2_common_types.VariableAttributeType> {
|
||||
createOrUpdateDeviceModelByStationId(
|
||||
tenantId: number,
|
||||
value: OCPP2_common_types.ReportDataType,
|
||||
ocppConnectionName: string,
|
||||
isoTimestamp: string,
|
||||
): Promise<VariableAttribute[]>;
|
||||
createOrUpdateByGetVariablesResultAndStationId(
|
||||
tenantId: number,
|
||||
getVariablesResult: OCPP2_common_types.GetVariableResultType[],
|
||||
ocppConnectionName: string,
|
||||
isoTimestamp: string,
|
||||
): Promise<VariableAttribute[]>;
|
||||
createOrUpdateBySetVariablesDataAndStationId(
|
||||
tenantId: number,
|
||||
setVariablesData: OCPP2_common_types.SetVariableDataType[],
|
||||
ocppConnectionName: string,
|
||||
isoTimestamp: string,
|
||||
): Promise<VariableAttribute[]>;
|
||||
updateResultByStationId(
|
||||
tenantId: number,
|
||||
result: OCPP2_common_types.SetVariableResultType,
|
||||
ocppConnectionName: string,
|
||||
isoTimestamp: string,
|
||||
existingVariableAttribute?: VariableAttribute,
|
||||
): Promise<VariableAttribute | undefined>;
|
||||
readAllSetVariableByStationId(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
): Promise<OCPP2_common_types.SetVariableDataType[]>;
|
||||
readAllByQuerystring(
|
||||
tenantId: number,
|
||||
query: VariableAttributeQuerystring,
|
||||
): Promise<VariableAttribute[]>;
|
||||
existByQuerystring(tenantId: number, query: VariableAttributeQuerystring): Promise<number>;
|
||||
deleteAllByQuerystring(
|
||||
tenantId: number,
|
||||
query: VariableAttributeQuerystring,
|
||||
): Promise<VariableAttribute[]>;
|
||||
findComponentAndVariable(
|
||||
tenantId: number,
|
||||
componentType: OCPP2_common_types.ComponentType,
|
||||
variableType: OCPP2_common_types.VariableType,
|
||||
): Promise<[Component | undefined, Variable | undefined]>;
|
||||
findOrCreateEvseAndComponentAndVariable(
|
||||
tenantId: number,
|
||||
componentType: OCPP2_common_types.ComponentType,
|
||||
variableType: OCPP2_common_types.VariableType,
|
||||
): Promise<[Component, Variable]>;
|
||||
findOrCreateEvseAndComponent(
|
||||
tenantId: number,
|
||||
componentType: OCPP2_common_types.ComponentType,
|
||||
ocppConnectionName: string,
|
||||
): Promise<Component>;
|
||||
findEvseByIdAndConnectorId(
|
||||
tenantId: number,
|
||||
id: number,
|
||||
connectorId: number | null,
|
||||
): Promise<EvseType | undefined>;
|
||||
findVariableCharacteristicsByVariableNameAndVariableInstance(
|
||||
tenantId: number,
|
||||
variableName: string,
|
||||
variableInstance: string | null,
|
||||
): Promise<VariableCharacteristics | undefined>;
|
||||
}
|
||||
|
||||
export interface ILocalAuthListRepository extends CrudRepository<LocalListVersion> {
|
||||
/**
|
||||
* Creates a SendLocalList.
|
||||
* @param {number} tenantId - The tenant ID.
|
||||
* @param ocppConnectionName - The connection name of the charging station
|
||||
* @param {string} correlationId - The correlation ID.
|
||||
* @param {UpdateEnumType} updateType - The type of update.
|
||||
* @param {number} versionNumber - The version number.
|
||||
* @param {AuthorizationData[]} localAuthorizationList - The list of authorizations.
|
||||
* @return {SendLocalList} The database object. Contains the correlationId to be used for the sendLocalListRequest.
|
||||
*/
|
||||
createSendLocalListFromRequestData(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
correlationId: string,
|
||||
updateType: UpdateEnumType,
|
||||
versionNumber: number,
|
||||
localAuthorizationList?: OCPP2_common_types.AuthorizationData[],
|
||||
): Promise<SendLocalList>;
|
||||
/**
|
||||
* Used to process GetLocalListVersionResponse, if version is unknown it will create or update LocalListVersion with the new version and an empty localAuthorizationList.
|
||||
* @param tenantId
|
||||
* @param versionNumber
|
||||
* @param ocppConnectionName - The connection name of the charging station
|
||||
*/
|
||||
validateOrReplaceLocalListVersionForStation(
|
||||
tenantId: number,
|
||||
versionNumber: number,
|
||||
ocppConnectionName: string,
|
||||
): Promise<void>;
|
||||
getSendLocalListRequestByStationIdAndCorrelationId(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
correlationId: string,
|
||||
): Promise<SendLocalList | undefined>;
|
||||
/**
|
||||
* Used to process SendLocalListResponse.
|
||||
* @param tenantId
|
||||
* @param ocppConnectionName - The connection name of the charging station
|
||||
* @param {SendLocalList} sendLocalList - The SendLocalList object created from the associated SendLocalListRequest.
|
||||
* @returns {LocalListVersion} LocalListVersion - The updated LocalListVersion.
|
||||
*/
|
||||
createOrUpdateLocalListVersionFromStationIdAndSendLocalList(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
sendLocalList: SendLocalList,
|
||||
): Promise<LocalListVersion>;
|
||||
}
|
||||
|
||||
export interface ILocationRepository extends CrudRepository<Location> {
|
||||
readLocationById: (tenantId: number, id: number) => Promise<Location | undefined>;
|
||||
readChargingStationByStationId: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
) => Promise<ChargingStation | undefined>;
|
||||
readConnectorByStationIdAndOcpp16ConnectorId: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
ocpp16ConnectorId: number,
|
||||
) => Promise<Connector | undefined>;
|
||||
readEvseByStationIdAndOcpp201EvseId: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
ocpp201EvseId: number,
|
||||
) => Promise<Evse | undefined>;
|
||||
readConnectorByStationIdAndOcpp201EvseType: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
ocpp201EvseType: OCPP2_common_types.EVSEType,
|
||||
) => Promise<Connector | undefined>;
|
||||
setChargingStationIsOnlineAndOCPPVersion: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
isOnline: boolean,
|
||||
ocppVersion: OCPPVersion | null,
|
||||
) => Promise<ChargingStation | undefined>;
|
||||
doesChargingStationExistByStationId: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
) => Promise<boolean>;
|
||||
addStatusNotificationToChargingStation(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
statusNotification: StatusNotification,
|
||||
): Promise<void>;
|
||||
createOrUpdateChargingStation(
|
||||
tenantId: number,
|
||||
chargingStation: ChargingStation,
|
||||
): Promise<ChargingStation>;
|
||||
createOrUpdateConnector(tenantId: number, connector: Connector): Promise<Connector | undefined>;
|
||||
/**
|
||||
* Commissions a default evse + evseTypeConnector record for an OCPP 1.6 connector.
|
||||
* Used in ad-hoc/`allowUnknownChargingStations` flows where the charge point arrives
|
||||
* uncommissioned (OCPP 1.6 has no native EVSE concept). Conservative default:
|
||||
* one connector → one evse. Returns the FK ids the caller should stamp on the
|
||||
* Connector record being upserted.
|
||||
*/
|
||||
commissionEvseForOcpp16Connector(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
connectorId: number,
|
||||
): Promise<{ evseId: number; evseTypeConnectorId: number }>;
|
||||
updateAllConnectorsByQuery(
|
||||
tenantId: number,
|
||||
value: Partial<Connector>,
|
||||
query: object,
|
||||
): Promise<Connector[]>;
|
||||
updateChargingStationTimestamp(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
timestamp: string,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ISecurityEventRepository {
|
||||
createByStationId: (
|
||||
tenantId: number,
|
||||
value: OCPP2_request_types.SecurityEventNotificationRequest,
|
||||
ocppConnectionName: string,
|
||||
) => Promise<SecurityEventDto>;
|
||||
readByStationIdAndTimestamps: (
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
from?: Date,
|
||||
to?: Date,
|
||||
) => Promise<SecurityEventDto[]>;
|
||||
deleteByKey: (tenantId: number, key: string) => Promise<SecurityEventDto | undefined>;
|
||||
}
|
||||
|
||||
export interface ISubscriptionRepository extends CrudRepository<Subscription> {
|
||||
create(tenantId: number, value: Subscription): Promise<Subscription>;
|
||||
readAllByStationId(tenantId: number, ocppConnectionName: string): Promise<Subscription[]>;
|
||||
deleteByKey(tenantId: number, key: string): Promise<Subscription | undefined>;
|
||||
}
|
||||
|
||||
export interface ITransactionEventRepository extends CrudRepository<TransactionEvent> {
|
||||
createOrUpdateTransactionByTransactionEventAndStationId(
|
||||
tenantId: number,
|
||||
value: OCPP2_request_types.TransactionEventRequest,
|
||||
ocppConnectionName: string,
|
||||
): Promise<Transaction>;
|
||||
createMeterValue(
|
||||
tenantId: number,
|
||||
value: OCPP2_common_types.MeterValueType,
|
||||
transactionDatabaseId?: number | null,
|
||||
transactionId?: string | null,
|
||||
tariffId?: number | null,
|
||||
): Promise<MeterValue>;
|
||||
createTransactionByStartTransaction(
|
||||
tenantId: number,
|
||||
request: OCPP1_6.StartTransactionRequest,
|
||||
ocppConnectionName: string,
|
||||
): Promise<Transaction>;
|
||||
updateTransactionByMeterValues(
|
||||
tenantId: number,
|
||||
meterValues: MeterValueDto[],
|
||||
ocppConnectionName: string,
|
||||
transactionId: number,
|
||||
): Promise<void>;
|
||||
readAllByStationIdAndTransactionId(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
transactionId: string,
|
||||
): Promise<TransactionEvent[]>;
|
||||
readTransactionByStationIdAndTransactionId(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
transactionId: string,
|
||||
): Promise<Transaction | undefined>;
|
||||
readAllTransactionsByStationIdAndEvseAndChargingStates(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
evse: OCPP2_common_types.EVSEType,
|
||||
chargingStates?: ChargingStateEnumType[],
|
||||
): Promise<Transaction[]>;
|
||||
readAllActiveTransactionsByAuthorizationId(
|
||||
tenantId: number,
|
||||
authorizationId: number,
|
||||
): Promise<Transaction[]>;
|
||||
readAllMeterValuesByTransactionDataBaseId(
|
||||
tenantId: number,
|
||||
transactionDataBaseId: number,
|
||||
): Promise<MeterValue[]>;
|
||||
getActiveTransactionByStationIdAndEvseId(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
evseId: number,
|
||||
): Promise<Transaction | undefined>;
|
||||
updateTransactionTotalCostById(tenantId: number, totalCost: number, id: number): Promise<void>;
|
||||
createStopTransaction(
|
||||
tenantId: number,
|
||||
transactionDatabaseId: number,
|
||||
ocppConnectionName: string,
|
||||
meterStop: number,
|
||||
timestamp: Date,
|
||||
meterValues: MeterValueDto[],
|
||||
reason?: string,
|
||||
idTokenDatabaseId?: number,
|
||||
): Promise<StopTransaction>;
|
||||
updateTransactionByStationIdAndTransactionId(
|
||||
tenantId: number,
|
||||
transaction: Partial<Transaction>,
|
||||
transactionId: string,
|
||||
ocppConnectionName: string,
|
||||
): Promise<Transaction | undefined>;
|
||||
deactivateActiveTransactionsByStationIdAndEvseId(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
evseId: number,
|
||||
excludeTransactionId: string,
|
||||
): Promise<Transaction[]>;
|
||||
}
|
||||
|
||||
export interface IVariableMonitoringRepository extends CrudRepository<VariableMonitoring> {
|
||||
createOrUpdateByMonitoringDataTypeAndStationId(
|
||||
tenantId: number,
|
||||
value: OCPP2_common_types.MonitoringDataType,
|
||||
componentId: string,
|
||||
variableId: string,
|
||||
ocppConnectionName: string,
|
||||
): Promise<VariableMonitoring[]>;
|
||||
createOrUpdateBySetMonitoringDataTypeAndStationId(
|
||||
tenantId: number,
|
||||
value: OCPP2_common_types.SetMonitoringDataType,
|
||||
componentId: string,
|
||||
variableId: string,
|
||||
ocppConnectionName: string,
|
||||
): Promise<VariableMonitoring>;
|
||||
rejectAllVariableMonitoringsByStationId(
|
||||
tenantId: number,
|
||||
action: CallAction,
|
||||
ocppConnectionName: string,
|
||||
): Promise<void>;
|
||||
rejectVariableMonitoringByIdAndStationId(
|
||||
tenantId: number,
|
||||
action: CallAction,
|
||||
id: number,
|
||||
ocppConnectionName: string,
|
||||
): Promise<void>;
|
||||
updateResultByStationId(
|
||||
tenantId: number,
|
||||
result: OCPP2_common_types.SetMonitoringResultType,
|
||||
ocppConnectionName: string,
|
||||
): Promise<VariableMonitoring>;
|
||||
createEventDatumByComponentIdAndVariableIdAndStationId(
|
||||
tenantId: number,
|
||||
event: OCPP2_common_types.EventDataType,
|
||||
componentId: string,
|
||||
variableId: string,
|
||||
ocppConnectionName: string,
|
||||
): Promise<EventData>;
|
||||
}
|
||||
|
||||
export interface IMessageInfoRepository extends CrudRepository<MessageInfo> {
|
||||
deactivateAllByStationId(tenantId: number, ocppConnectionName: string): Promise<void>;
|
||||
createOrUpdateByMessageInfoTypeAndStationId(
|
||||
tenantId: number,
|
||||
value: OCPP2_common_types.MessageInfoType,
|
||||
ocppConnectionName: string,
|
||||
componentId?: number,
|
||||
): Promise<MessageInfo>;
|
||||
}
|
||||
|
||||
export interface ITariffRepository extends CrudRepository<Tariff> {
|
||||
findByConnectorId(tenantId: number, connectorId: number): Promise<Tariff | undefined>;
|
||||
readAllByQuerystring(tenantId: number, query: TariffQueryString): Promise<Tariff[]>;
|
||||
deleteAllByQuerystring(tenantId: number, query: TariffQueryString): Promise<Tariff[]>;
|
||||
upsertTariff(tenantId: number, tariff: Tariff): Promise<Tariff>;
|
||||
upsertTariffByTariffId(tenantId: number, tariff: Tariff): Promise<Tariff>;
|
||||
}
|
||||
|
||||
export interface ICertificateRepository extends CrudRepository<Certificate> {
|
||||
createOrUpdateCertificate(tenantId: number, certificate: Certificate): Promise<Certificate>;
|
||||
}
|
||||
|
||||
export interface IInstalledCertificateRepository extends CrudRepository<InstalledCertificate> {}
|
||||
export interface IInstallCertificateAttemptRepository
|
||||
extends CrudRepository<InstallCertificateAttempt> {}
|
||||
export interface IDeleteCertificateAttemptRepository
|
||||
extends CrudRepository<DeleteCertificateAttempt> {}
|
||||
|
||||
export interface IChargingProfileRepository extends CrudRepository<ChargingProfile> {
|
||||
createOrUpdateChargingProfile(
|
||||
tenantId: number,
|
||||
chargingProfile: ChargingProfileInput,
|
||||
ocppConnectionName: string,
|
||||
evseId?: number | null,
|
||||
chargingLimitSource?: ChargingLimitSourceEnumType,
|
||||
isActive?: boolean,
|
||||
): Promise<ChargingProfile>;
|
||||
createChargingNeeds(
|
||||
tenantId: number,
|
||||
chargingNeeds: OCPP2_request_types.NotifyEVChargingNeedsRequest,
|
||||
ocppConnectionName: string,
|
||||
): Promise<ChargingNeeds>;
|
||||
findChargingNeedsByEvseDBIdAndTransactionDBId(
|
||||
tenantId: number,
|
||||
evseDBId: number,
|
||||
transactionDataBaseId: number,
|
||||
): Promise<ChargingNeeds | undefined>;
|
||||
createCompositeSchedule(
|
||||
tenantId: number,
|
||||
compositeSchedule: CompositeScheduleInput,
|
||||
ocppConnectionName: string,
|
||||
): Promise<CompositeSchedule>;
|
||||
getNextChargingProfileId(tenantId: number, ocppConnectionName: string): Promise<number>;
|
||||
getNextChargingScheduleId(tenantId: number, ocppConnectionName: string): Promise<number>;
|
||||
getNextStackLevel(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
transactionDatabaseId: number | null,
|
||||
profilePurpose: ChargingProfilePurposeEnumType,
|
||||
): Promise<number>;
|
||||
}
|
||||
|
||||
export interface IReservationRepository extends CrudRepository<Reservation> {
|
||||
createOrUpdateReservation(
|
||||
tenantId: number,
|
||||
reserveNowRequest: OCPP2_request_types.ReserveNowRequest,
|
||||
ocppConnectionName: string,
|
||||
isActive?: boolean,
|
||||
): Promise<Reservation | undefined>;
|
||||
}
|
||||
|
||||
export interface IOCPPMessageRepository extends CrudRepository<OCPPMessage> {
|
||||
createOCPPMessage(tenantId: number, message: OCPPMessageDto): Promise<OCPPMessage>;
|
||||
getRequestByCorrelationId(
|
||||
tenantId: number,
|
||||
correlationId: string,
|
||||
): Promise<OCPPMessage | undefined>;
|
||||
}
|
||||
|
||||
export interface IChargingStationSecurityInfoRepository
|
||||
extends CrudRepository<ChargingStationSecurityInfo> {
|
||||
readChargingStationPublicKeyFileId(tenantId: number, ocppConnectionName: string): Promise<string>;
|
||||
readOrCreateChargingStationInfo(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
publicKeyFileId: string,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IChargingStationSequenceRepository
|
||||
extends CrudRepository<ChargingStationSequence> {
|
||||
getNextSequenceValue(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
type: ChargingStationSequenceTypeEnumType,
|
||||
): Promise<number>;
|
||||
}
|
||||
|
||||
export interface IServerNetworkProfileRepository extends CrudRepository<ServerNetworkProfile> {
|
||||
upsertServerNetworkProfile(
|
||||
websocketServerConfig: any,
|
||||
maxCallLengthSeconds: number,
|
||||
): Promise<ServerNetworkProfile>;
|
||||
}
|
||||
|
||||
export interface IChangeConfigurationRepository extends CrudRepository<ChangeConfiguration> {
|
||||
createOrUpdateChangeConfiguration(
|
||||
tenantId: number,
|
||||
configuration: ChangeConfiguration,
|
||||
): Promise<ChangeConfiguration | undefined>;
|
||||
}
|
||||
|
||||
export interface ITenantRepository extends CrudRepository<Tenant> {
|
||||
createTenant(tenant: Tenant): Promise<Tenant>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { DefaultDrizzleInstance } from './util.js';
|
||||
export { DrizzleRepository } from './repository/Base.js';
|
||||
export { DrizzleSecurityEventRepository, toSecurityEventDto } from './repository/SecurityEvent.js';
|
||||
export {
|
||||
securityEventTable,
|
||||
tenantSecurityEventTable,
|
||||
SecurityEventEntitySchema,
|
||||
SecurityEventEntityInsertSchema,
|
||||
type SecurityEventEntity,
|
||||
type SecurityEventEntityInsert,
|
||||
// Legacy TypeScript-only types
|
||||
type SecurityEventSelect,
|
||||
type SecurityEventInsert,
|
||||
} from './schema/SecurityEvent.js';
|
||||
@@ -0,0 +1,167 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BootstrapConfig } from '@citrineos/base';
|
||||
import { and, count, eq, type Column, type InferSelectModel } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import type { PgTable } from 'drizzle-orm/pg-core';
|
||||
import EventEmitter from 'events';
|
||||
import { type ILogObj, Logger } from 'tslog';
|
||||
import { DefaultDrizzleInstance } from '../util.js';
|
||||
|
||||
// Every CitrineOS table shares these two columns — used to implement common
|
||||
// query patterns (findById, deleteById, etc.) in the base class without casting.
|
||||
export type CitrineTable = PgTable & {
|
||||
id: Column;
|
||||
tenantId: Column;
|
||||
};
|
||||
|
||||
export abstract class DrizzleRepository<TTable extends CitrineTable, TDto> extends EventEmitter {
|
||||
protected readonly db: NodePgDatabase;
|
||||
protected readonly logger: Logger<ILogObj>;
|
||||
|
||||
// When true, queries target a per-tenant Postgres schema ("tenant_X"."Table")
|
||||
// and the tenantId column filter is omitted — the schema is the isolation boundary.
|
||||
protected readonly useTenantSchema: boolean;
|
||||
|
||||
constructor(
|
||||
config: BootstrapConfig,
|
||||
logger?: Logger<ILogObj>,
|
||||
db?: NodePgDatabase,
|
||||
useTenantSchema = false,
|
||||
) {
|
||||
super();
|
||||
this.db = db ?? DefaultDrizzleInstance.getInstance(config, logger);
|
||||
this.logger = logger
|
||||
? logger.getSubLogger({ name: this.constructor.name })
|
||||
: new Logger<ILogObj>({ name: this.constructor.name });
|
||||
this.useTenantSchema = useTenantSchema;
|
||||
}
|
||||
|
||||
// Subclasses return either the public-schema table (row-level tenancy) or a
|
||||
// schema-qualified table (schema-per-tenant). Every shared method calls this,
|
||||
// so tenancy mode is transparent to callers.
|
||||
protected abstract getTable(tenantId: number): TTable;
|
||||
|
||||
// Subclasses map raw DB rows to clean DTO objects — no ORM leakage.
|
||||
protected abstract toDto(row: InferSelectModel<TTable>): TDto;
|
||||
|
||||
// Returns the tenant isolation predicate for WHERE clauses.
|
||||
// Undefined in schema-per-tenant mode because isolation lives at the schema level.
|
||||
private tenantFilter(table: TTable, tenantId: number) {
|
||||
return this.useTenantSchema ? undefined : eq(table.tenantId, tenantId);
|
||||
}
|
||||
|
||||
// ─── Shared read methods ──────────────────────────────────────────────────
|
||||
|
||||
async findById(tenantId: number, id: number): Promise<TDto | undefined> {
|
||||
const table = this.getTable(tenantId);
|
||||
const filter = this.tenantFilter(table, tenantId);
|
||||
const where = filter ? and(eq(table.id, id), filter) : eq(table.id, id);
|
||||
|
||||
// `as any` on table: Drizzle's from() has internal generic constraints
|
||||
// (TableLikeHasEmptySelection) that don't resolve for bounded generic PgTables.
|
||||
// The public return type is fully typed via TDto.
|
||||
const rows = (await this.db
|
||||
.select()
|
||||
.from(table as any)
|
||||
.where(where)
|
||||
.limit(1)) as InferSelectModel<TTable>[];
|
||||
|
||||
return rows[0] ? this.toDto(rows[0]) : undefined;
|
||||
}
|
||||
|
||||
async findAll(tenantId: number): Promise<TDto[]> {
|
||||
const table = this.getTable(tenantId);
|
||||
const filter = this.tenantFilter(table, tenantId);
|
||||
|
||||
const rows = (
|
||||
filter
|
||||
? await this.db
|
||||
.select()
|
||||
.from(table as any)
|
||||
.where(filter)
|
||||
: await this.db.select().from(table as any)
|
||||
) as InferSelectModel<TTable>[];
|
||||
|
||||
return rows.map((row) => this.toDto(row));
|
||||
}
|
||||
|
||||
async exists(tenantId: number, id: number): Promise<boolean> {
|
||||
const table = this.getTable(tenantId);
|
||||
const filter = this.tenantFilter(table, tenantId);
|
||||
const where = filter ? and(eq(table.id, id), filter) : eq(table.id, id);
|
||||
|
||||
const rows = await this.db
|
||||
.select({ id: table.id as any })
|
||||
.from(table as any)
|
||||
.where(where)
|
||||
.limit(1);
|
||||
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
async countAll(tenantId: number): Promise<number> {
|
||||
const table = this.getTable(tenantId);
|
||||
const filter = this.tenantFilter(table, tenantId);
|
||||
|
||||
const result = filter
|
||||
? await this.db
|
||||
.select({ count: count() })
|
||||
.from(table as any)
|
||||
.where(filter)
|
||||
: await this.db.select({ count: count() }).from(table as any);
|
||||
|
||||
return result[0]?.count ?? 0;
|
||||
}
|
||||
|
||||
// ─── Shared write methods (all emit events) ───────────────────────────────
|
||||
|
||||
// values is typed as object here because InferInsertModel<TTable> with a generic
|
||||
// TTable hits TypeScript inference limits. Subclasses expose typed create methods
|
||||
// (e.g. createByStationId) that call this internally with the correct shape.
|
||||
protected async insert(tenantId: number, values: object): Promise<TDto> {
|
||||
const table = this.getTable(tenantId);
|
||||
|
||||
const rows = (await (this.db.insert(table as any) as any)
|
||||
.values({ ...values, tenantId })
|
||||
.returning()) as InferSelectModel<TTable>[];
|
||||
|
||||
const dto = this.toDto(rows[0]);
|
||||
this.emit('created', [dto]);
|
||||
return dto;
|
||||
}
|
||||
|
||||
// values is typed as object for the same reason as insert above.
|
||||
async updateById(tenantId: number, id: number, values: object): Promise<TDto | undefined> {
|
||||
const table = this.getTable(tenantId);
|
||||
const filter = this.tenantFilter(table, tenantId);
|
||||
const where = filter ? and(eq(table.id, id), filter) : eq(table.id, id);
|
||||
|
||||
const rows = (await (this.db.update(table as any) as any)
|
||||
.set(values)
|
||||
.where(where)
|
||||
.returning()) as InferSelectModel<TTable>[];
|
||||
|
||||
if (!rows[0]) return undefined;
|
||||
const dto = this.toDto(rows[0]);
|
||||
this.emit('updated', [dto]);
|
||||
return dto;
|
||||
}
|
||||
|
||||
async deleteById(tenantId: number, id: number): Promise<TDto | undefined> {
|
||||
const table = this.getTable(tenantId);
|
||||
const filter = this.tenantFilter(table, tenantId);
|
||||
const where = filter ? and(eq(table.id, id), filter) : eq(table.id, id);
|
||||
|
||||
const rows = (await (this.db.delete(table as any) as any)
|
||||
.where(where)
|
||||
.returning()) as InferSelectModel<TTable>[];
|
||||
|
||||
if (!rows[0]) return undefined;
|
||||
const dto = this.toDto(rows[0]);
|
||||
this.emit('deleted', [dto]);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ISecurityEventRepository } from '@/dal/index.js';
|
||||
import type { BootstrapConfig, SecurityEventDto } from '@citrineos/base';
|
||||
import { OCPP2_0_1 } from '@citrineos/base';
|
||||
import { and, between, eq, gte, lte } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import type { ILogObj } from 'tslog';
|
||||
import { Logger } from 'tslog';
|
||||
import {
|
||||
type SecurityEventEntity,
|
||||
securityEventTable,
|
||||
tenantSecurityEventTable,
|
||||
} from '../schema/SecurityEvent.js';
|
||||
import { type Explicit } from '../types.js';
|
||||
import { DrizzleRepository } from './Base.js';
|
||||
|
||||
// ─── Mapper ──────────────────────────────────────────────────────────────────
|
||||
// Maps a Drizzle entity (DB row, validated by SecurityEventEntitySchema) to the
|
||||
// external SecurityEventDto contract. This keeps the ORM type contained to the
|
||||
// DAL layer while letting the rest of the system work against stable DTOs.
|
||||
//
|
||||
// Explicit<SecurityEventDto> is used so TypeScript errors if any field — including
|
||||
// optional ones — is forgotten. The value may still be undefined, but it must be
|
||||
// consciously declared. See ../types.ts for the full rationale.
|
||||
export function toSecurityEventDto(entity: SecurityEventEntity): SecurityEventDto {
|
||||
const dto: Explicit<SecurityEventDto> = {
|
||||
id: entity.id,
|
||||
ocppConnectionName: entity.ocppConnectionName,
|
||||
type: entity.type ?? '',
|
||||
// Drizzle returns timestamp as JS Date (mode: 'date'); DTO contract is ISO string.
|
||||
timestamp: entity.timestamp.toISOString(),
|
||||
techInfo: entity.techInfo ?? null,
|
||||
tenantId: entity.tenantId,
|
||||
tenant: undefined,
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
};
|
||||
return dto;
|
||||
}
|
||||
|
||||
export class DrizzleSecurityEventRepository
|
||||
extends DrizzleRepository<typeof securityEventTable, SecurityEventDto>
|
||||
implements ISecurityEventRepository
|
||||
{
|
||||
constructor(
|
||||
config: BootstrapConfig,
|
||||
logger?: Logger<ILogObj>,
|
||||
db?: NodePgDatabase,
|
||||
useTenantSchema = false,
|
||||
) {
|
||||
super(config, logger, db, useTenantSchema);
|
||||
}
|
||||
|
||||
protected getTable(tenantId: number): typeof securityEventTable {
|
||||
return this.useTenantSchema ? tenantSecurityEventTable(tenantId) : securityEventTable;
|
||||
}
|
||||
|
||||
protected toDto(row: SecurityEventEntity): SecurityEventDto {
|
||||
return toSecurityEventDto(row);
|
||||
}
|
||||
|
||||
// ─── ISecurityEventRepository methods ────────────────────────────────────
|
||||
|
||||
async createByStationId(
|
||||
tenantId: number,
|
||||
value: OCPP2_0_1.SecurityEventNotificationRequest,
|
||||
ocppConnectionName: string,
|
||||
): Promise<SecurityEventDto> {
|
||||
// Delegates to base.insert() which handles tenantId injection and event emission.
|
||||
// OCPP delivers timestamp as ISO string; Postgres expects a Date for timestamptz.
|
||||
return this.insert(tenantId, {
|
||||
ocppConnectionName,
|
||||
type: value.type,
|
||||
timestamp: new Date(value.timestamp),
|
||||
techInfo: value.techInfo ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
async readByStationIdAndTimestamps(
|
||||
tenantId: number,
|
||||
ocppConnectionName: string,
|
||||
from?: Date,
|
||||
to?: Date,
|
||||
): Promise<SecurityEventDto[]> {
|
||||
const table = this.getTable(tenantId);
|
||||
|
||||
const conditions = [eq(table.ocppConnectionName, ocppConnectionName)];
|
||||
|
||||
if (!this.useTenantSchema) {
|
||||
conditions.push(eq(table.tenantId, tenantId));
|
||||
}
|
||||
|
||||
if (from && to) {
|
||||
conditions.push(between(table.timestamp, from, to));
|
||||
} else if (from) {
|
||||
conditions.push(gte(table.timestamp, from));
|
||||
} else if (to) {
|
||||
conditions.push(lte(table.timestamp, to));
|
||||
}
|
||||
|
||||
const rows = await this.db
|
||||
.select()
|
||||
.from(table)
|
||||
.where(and(...conditions));
|
||||
return rows.map((row) => this.toDto(row as SecurityEventEntity));
|
||||
}
|
||||
|
||||
async deleteByKey(tenantId: number, key: string): Promise<SecurityEventDto | undefined> {
|
||||
return this.deleteById(tenantId, Number(key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { TableName } from '@dal/layers/sequelize/model/TableName.js';
|
||||
import { index, integer, pgSchema, pgTable, serial, timestamp, varchar } from 'drizzle-orm/pg-core';
|
||||
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
||||
import { type z } from 'zod';
|
||||
|
||||
// Column definitions are a function to ensure fresh objects per table instance,
|
||||
// which is required when the same schema is used across multiple pgSchema() calls.
|
||||
function securityEventColumns() {
|
||||
return {
|
||||
id: serial('id').primaryKey(),
|
||||
ocppConnectionName: varchar('ocppConnectionName', { length: 255 }).notNull(),
|
||||
type: varchar('type', { length: 255 }),
|
||||
// mode: 'date' returns a JS Date — mapped to ISO string in the repository layer
|
||||
timestamp: timestamp('timestamp', { withTimezone: true, mode: 'date' }).notNull(),
|
||||
techInfo: varchar('techInfo', { length: 255 }),
|
||||
tenantId: integer('tenantId').notNull(),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true, mode: 'date' })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: timestamp('updatedAt', { withTimezone: true, mode: 'date' })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
};
|
||||
}
|
||||
|
||||
// Row-level tenancy (current approach): single public schema, tenantId column filter on every query
|
||||
export const securityEventTable = pgTable(TableName.SecurityEvents, securityEventColumns(), (t) => [
|
||||
index('security_events_ocpp_connection_name').on(t.ocppConnectionName),
|
||||
]);
|
||||
|
||||
// Schema-per-tenant (future approach): one Postgres schema per tenant, no tenantId filter needed
|
||||
const tenantTableCache = new Map<number, typeof securityEventTable>();
|
||||
|
||||
// Returns a schema-qualified table reference for schema-per-tenant queries.
|
||||
// Cast to typeof securityEventTable so Drizzle's query builder can infer correct
|
||||
// return types — the column structure is identical, only the schema name differs at runtime.
|
||||
export function tenantSecurityEventTable(tenantId: number): typeof securityEventTable {
|
||||
if (!tenantTableCache.has(tenantId)) {
|
||||
const t = pgSchema(`tenant_${tenantId}`).table(
|
||||
TableName.SecurityEvents,
|
||||
securityEventColumns(),
|
||||
) as unknown as typeof securityEventTable;
|
||||
tenantTableCache.set(tenantId, t);
|
||||
}
|
||||
return tenantTableCache.get(tenantId)!;
|
||||
}
|
||||
|
||||
// ─── Zod schemas (runtime validation + type inference) ───────────────────────
|
||||
|
||||
// Entity schema: represents a fully-hydrated row read from the database.
|
||||
export const SecurityEventEntitySchema = createSelectSchema(securityEventTable);
|
||||
|
||||
// Insert schema: represents the subset of fields required/accepted on write.
|
||||
// drizzle-zod automatically makes columns with $defaultFn optional here.
|
||||
export const SecurityEventEntityInsertSchema = createInsertSchema(securityEventTable);
|
||||
|
||||
export type SecurityEventEntity = z.infer<typeof SecurityEventEntitySchema>;
|
||||
export type SecurityEventEntityInsert = z.infer<typeof SecurityEventEntityInsertSchema>;
|
||||
|
||||
// Legacy TypeScript types kept for backward compatibility — prefer the Zod types above.
|
||||
export type SecurityEventSelect = typeof securityEventTable.$inferSelect;
|
||||
export type SecurityEventInsert = typeof securityEventTable.$inferInsert;
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Forces every key of T to be explicitly present in the object literal,
|
||||
* even optional ones — the value can still be undefined, but it must be declared.
|
||||
*
|
||||
* Why: TypeScript silently allows optional keys to be omitted from object
|
||||
* literals that satisfy a type with `?` properties. This means a mapper that
|
||||
* adds a new optional field to a DTO will not break existing mapper
|
||||
* implementations — the field will simply be absent at runtime, which can cause
|
||||
* subtle bugs. Typing the intermediate result as `Explicit<T>` turns those
|
||||
* silent omissions into compile errors, forcing every developer to make a
|
||||
* conscious decision about each field (even if that decision is `field: undefined`).
|
||||
*/
|
||||
export type Explicit<T> = { [K in keyof Required<T>]: T[K] };
|
||||
@@ -0,0 +1,74 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BootstrapConfig } from '@citrineos/base';
|
||||
import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import pg from 'pg';
|
||||
import type { Pool } from 'pg';
|
||||
import { type ILogObj, Logger } from 'tslog';
|
||||
|
||||
export class DefaultDrizzleInstance {
|
||||
private static readonly DEFAULT_RETRIES = 5;
|
||||
private static readonly DEFAULT_RETRY_DELAY = 5000;
|
||||
private static instance: NodePgDatabase | null = null;
|
||||
private static pool: Pool | null = null;
|
||||
private static logger: Logger<ILogObj>;
|
||||
private static config: BootstrapConfig;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(config: BootstrapConfig, logger?: Logger<ILogObj>): NodePgDatabase {
|
||||
if (!DefaultDrizzleInstance.instance) {
|
||||
DefaultDrizzleInstance.config = config;
|
||||
DefaultDrizzleInstance.logger = logger
|
||||
? logger.getSubLogger({ name: this.name })
|
||||
: new Logger<ILogObj>({ name: this.name });
|
||||
|
||||
DefaultDrizzleInstance.pool = new pg.Pool({
|
||||
host: config.database.host,
|
||||
port: config.database.port,
|
||||
database: config.database.database,
|
||||
user: config.database.username,
|
||||
password: config.database.password,
|
||||
max: config.database.pool?.max,
|
||||
min: config.database.pool?.min,
|
||||
idleTimeoutMillis: config.database.pool?.idle,
|
||||
connectionTimeoutMillis: config.database.pool?.acquire,
|
||||
...(config.database.ssl && { ssl: config.database.ssl }),
|
||||
});
|
||||
|
||||
DefaultDrizzleInstance.instance = drizzle(DefaultDrizzleInstance.pool);
|
||||
}
|
||||
return DefaultDrizzleInstance.instance;
|
||||
}
|
||||
|
||||
public static async initialize(): Promise<void> {
|
||||
const maxRetries = this.config.database.maxRetries ?? this.DEFAULT_RETRIES;
|
||||
const retryDelay = this.config.database.retryDelay ?? this.DEFAULT_RETRY_DELAY;
|
||||
let retryCount = 0;
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
const client = await this.pool!.connect();
|
||||
client.release();
|
||||
this.logger.info('Drizzle database connection established successfully');
|
||||
break;
|
||||
} catch (error) {
|
||||
retryCount++;
|
||||
this.logger.error(
|
||||
`Failed to connect to database via Drizzle (attempt ${retryCount}/${maxRetries}):`,
|
||||
error,
|
||||
);
|
||||
if (retryCount < maxRetries) {
|
||||
this.logger.info(`Retrying in ${retryDelay / 1000} seconds...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
} else {
|
||||
this.logger.error(
|
||||
'Max retries reached. Unable to establish Drizzle database connection.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Sequelize Persistence Models
|
||||
export { Boot } from './model/Boot.js';
|
||||
export {
|
||||
VariableAttribute,
|
||||
VariableCharacteristics,
|
||||
Component,
|
||||
EvseType,
|
||||
Variable,
|
||||
VariableStatus,
|
||||
} from './model/DeviceModel/index.js';
|
||||
export {
|
||||
Authorization,
|
||||
LocalListAuthorization,
|
||||
LocalListVersion,
|
||||
SendLocalList,
|
||||
LocalListVersionAuthorization,
|
||||
SendLocalListAuthorization,
|
||||
} from './model/Authorization/index.js';
|
||||
export {
|
||||
StartTransaction,
|
||||
StopTransaction,
|
||||
Transaction,
|
||||
TransactionEvent,
|
||||
MeterValue,
|
||||
} from './model/TransactionEvent/index.js';
|
||||
export { SecurityEvent } from './model/SecurityEvent.js';
|
||||
export {
|
||||
VariableMonitoring,
|
||||
EventData,
|
||||
VariableMonitoringStatus,
|
||||
} from './model/VariableMonitoring/index.js';
|
||||
export {
|
||||
ChargingStation,
|
||||
Evse,
|
||||
ChargingStationNetworkProfile,
|
||||
LatestStatusNotification,
|
||||
Location,
|
||||
ServerNetworkProfile,
|
||||
SetNetworkProfile,
|
||||
StatusNotification,
|
||||
Connector,
|
||||
} from './model/Location/index.js';
|
||||
export { ChargingStationSequence } from './model/ChargingStationSequence/index.js';
|
||||
export { MessageInfo } from './model/MessageInfo/index.js';
|
||||
export { Tariff } from './model/Tariff/index.js';
|
||||
export { Subscription } from './model/Subscription/index.js';
|
||||
export {
|
||||
Certificate,
|
||||
SignatureAlgorithmEnumType,
|
||||
CountryNameEnumType,
|
||||
InstalledCertificate,
|
||||
} from './model/Certificate/index.js';
|
||||
export {
|
||||
ChargingProfile,
|
||||
ChargingNeeds,
|
||||
ChargingSchedule,
|
||||
CompositeSchedule,
|
||||
SalesTariff,
|
||||
} from './model/ChargingProfile/index.js';
|
||||
export { OCPPMessage } from './model/OCPPMessage.js';
|
||||
export { Reservation } from './model/Reservation.js';
|
||||
export { ChargingStationSecurityInfo } from './model/ChargingStationSecurityInfo.js';
|
||||
export { ChangeConfiguration } from './model/ChangeConfiguration.js';
|
||||
export { Tenant } from './model/Tenant.js';
|
||||
export { TenantPartner } from './model/TenantPartner.js';
|
||||
export type { PaginatedParams } from './model/AsyncJob/index.js';
|
||||
export { AsyncJobStatus, AsyncJobStatusDTO, AsyncJobRequest } from './model/AsyncJob/index.js';
|
||||
export { DeleteCertificateAttempt, InstallCertificateAttempt } from './model/Certificate/index.js';
|
||||
|
||||
// Sequelize Repositories
|
||||
export { SequelizeRepository } from './repository/Base.js';
|
||||
export { SequelizeAuthorizationRepository } from './repository/Authorization.js';
|
||||
export { SequelizeBootRepository } from './repository/Boot.js';
|
||||
export { SequelizeDeviceModelRepository } from './repository/DeviceModel.js';
|
||||
export { SequelizeLocalAuthListRepository } from './repository/LocalAuthList.js';
|
||||
export { SequelizeLocationRepository } from './repository/Location.js';
|
||||
export { SequelizeTransactionEventRepository } from './repository/TransactionEvent.js';
|
||||
export { SequelizeSecurityEventRepository } from './repository/SecurityEvent.js';
|
||||
export { SequelizeVariableMonitoringRepository } from './repository/VariableMonitoring.js';
|
||||
export { SequelizeMessageInfoRepository } from './repository/MessageInfo.js';
|
||||
export { SequelizeTariffRepository } from './repository/Tariff.js';
|
||||
export { SequelizeSubscriptionRepository } from './repository/Subscription.js';
|
||||
export { SequelizeCertificateRepository } from './repository/Certificate.js';
|
||||
export { SequelizeInstalledCertificateRepository } from './repository/InstalledCertificate.js';
|
||||
export { SequelizeChargingProfileRepository } from './repository/ChargingProfile.js';
|
||||
export { SequelizeOCPPMessageRepository } from './repository/OCPPMessage.js';
|
||||
export { SequelizeReservationRepository } from './repository/Reservation.js';
|
||||
export { SequelizeChargingStationSecurityInfoRepository } from './repository/ChargingStationSecurityInfo.js';
|
||||
export { SequelizeChargingStationSequenceRepository } from './repository/ChargingStationSequence.js';
|
||||
export { SequelizeChangeConfigurationRepository } from './repository/ChangeConfiguration.js';
|
||||
export { SequelizeTenantRepository } from './repository/Tenant.js';
|
||||
export { SequelizeAsyncJobStatusRepository } from './repository/AsyncJobStatus.js';
|
||||
export { SequelizeServerNetworkProfileRepository } from './repository/ServerNetworkProfile.js';
|
||||
export { SequelizeInstallCertificateAttemptRepository } from './repository/InstallCertificateAttempt.js';
|
||||
export { SequelizeDeleteCertificateAttemptRepository } from './repository/DeleteCertificateAttempt.js';
|
||||
|
||||
// Sequelize Utilities
|
||||
export { DefaultSequelizeInstance } from './util.js';
|
||||
|
||||
// Sequelize Mappers
|
||||
export * as OCPP2_0_1_Mapper from './mapper/2.0.1/index.js';
|
||||
export * as OCPP1_6_Mapper from './mapper/1.6/index.js';
|
||||
export * as OCPP2_1_Mapper from './mapper/2.1/index.js';
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AuthorizationStatusEnumType } from '@citrineos/base';
|
||||
import { AuthorizationStatusEnum, OCPP1_6 } from '@citrineos/base';
|
||||
|
||||
export class AuthorizationMapper {
|
||||
static toIdTagInfoStatus(status: AuthorizationStatusEnumType): OCPP1_6.AuthorizeResponseStatus {
|
||||
switch (status) {
|
||||
case AuthorizationStatusEnum.Accepted:
|
||||
return OCPP1_6.AuthorizeResponseStatus.Accepted;
|
||||
case AuthorizationStatusEnum.Blocked:
|
||||
return OCPP1_6.AuthorizeResponseStatus.Blocked;
|
||||
case AuthorizationStatusEnum.Expired:
|
||||
return OCPP1_6.AuthorizeResponseStatus.Expired;
|
||||
case AuthorizationStatusEnum.Invalid:
|
||||
return OCPP1_6.AuthorizeResponseStatus.Invalid;
|
||||
default:
|
||||
throw new Error('Unknown IdTagInfoStatus status');
|
||||
}
|
||||
}
|
||||
|
||||
static toStartTransactionResponseStatus(
|
||||
status: AuthorizationStatusEnumType,
|
||||
): OCPP1_6.StartTransactionResponseStatus {
|
||||
switch (status) {
|
||||
case AuthorizationStatusEnum.Accepted:
|
||||
return OCPP1_6.StartTransactionResponseStatus.Accepted;
|
||||
case AuthorizationStatusEnum.Blocked:
|
||||
return OCPP1_6.StartTransactionResponseStatus.Blocked;
|
||||
case AuthorizationStatusEnum.ConcurrentTx:
|
||||
return OCPP1_6.StartTransactionResponseStatus.ConcurrentTx;
|
||||
case AuthorizationStatusEnum.Expired:
|
||||
return OCPP1_6.StartTransactionResponseStatus.Expired;
|
||||
case AuthorizationStatusEnum.Invalid:
|
||||
return OCPP1_6.StartTransactionResponseStatus.Invalid;
|
||||
default:
|
||||
throw new Error('Unknown StartTransactionResponse status');
|
||||
}
|
||||
}
|
||||
}
|
||||
20
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/BootMapper.ts
vendored
Normal file
20
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/BootMapper.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { OCPP1_6 } from '@citrineos/base';
|
||||
|
||||
export class BootMapper {
|
||||
static toRegistrationStatusEnumType(status: string): OCPP1_6.BootNotificationResponseStatus {
|
||||
switch (status) {
|
||||
case 'Accepted':
|
||||
return OCPP1_6.BootNotificationResponseStatus.Accepted;
|
||||
case 'Pending':
|
||||
return OCPP1_6.BootNotificationResponseStatus.Pending;
|
||||
case 'Rejected':
|
||||
return OCPP1_6.BootNotificationResponseStatus.Rejected;
|
||||
default:
|
||||
throw new Error(`Invalid status: ${status}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
163
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/ChargingProfileMapper.ts
vendored
Normal file
163
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/ChargingProfileMapper.ts
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
ChargingProfileKindEnum,
|
||||
ChargingProfilePurposeEnum,
|
||||
ChargingRateUnitEnum,
|
||||
OCPP1_6,
|
||||
RecurrencyKindEnum,
|
||||
} from '@citrineos/base';
|
||||
|
||||
/**
|
||||
* Input type for creating/updating a ChargingProfile via the repository.
|
||||
* Uses native enum types.
|
||||
*/
|
||||
export interface ChargingProfileInput {
|
||||
id: number;
|
||||
stackLevel: number;
|
||||
chargingProfilePurpose: keyof typeof ChargingProfilePurposeEnum;
|
||||
chargingProfileKind: keyof typeof ChargingProfileKindEnum;
|
||||
recurrencyKind?: keyof typeof RecurrencyKindEnum | null;
|
||||
validFrom?: string | null;
|
||||
validTo?: string | null;
|
||||
chargingSchedule:
|
||||
| [ChargingScheduleInput]
|
||||
| [ChargingScheduleInput, ChargingScheduleInput]
|
||||
| [ChargingScheduleInput, ChargingScheduleInput, ChargingScheduleInput];
|
||||
transactionId?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input type for creating a ChargingSchedule via the repository.
|
||||
* Uses native enum types.
|
||||
*/
|
||||
export interface ChargingScheduleInput {
|
||||
id: number;
|
||||
startSchedule?: string | null;
|
||||
duration?: number | null;
|
||||
chargingRateUnit: keyof typeof ChargingRateUnitEnum;
|
||||
chargingSchedulePeriod: [ChargingSchedulePeriodInput, ...ChargingSchedulePeriodInput[]];
|
||||
minChargingRate?: number | null;
|
||||
}
|
||||
|
||||
export interface ChargingSchedulePeriodInput {
|
||||
startPeriod: number;
|
||||
limit: number;
|
||||
numberPhases?: number | null;
|
||||
}
|
||||
|
||||
export class ChargingProfileMapper {
|
||||
/**
|
||||
* OCPP 1.6 'ChargePointMaxProfile' maps to native 'ChargingStationMaxProfile'.
|
||||
* All other enum values are identical and are type-safe casts.
|
||||
*/
|
||||
static fromChargingProfilePurpose(purpose: string): keyof typeof ChargingProfilePurposeEnum {
|
||||
if (purpose === 'ChargePointMaxProfile') {
|
||||
return 'ChargingStationMaxProfile';
|
||||
}
|
||||
return purpose as keyof typeof ChargingProfilePurposeEnum;
|
||||
}
|
||||
|
||||
static fromChargingProfileKind(kind: string): keyof typeof ChargingProfileKindEnum {
|
||||
return kind as unknown as keyof typeof ChargingProfileKindEnum;
|
||||
}
|
||||
|
||||
static fromRecurrencyKind(kind?: string | null): keyof typeof RecurrencyKindEnum | undefined {
|
||||
if (!kind) return undefined;
|
||||
return kind as unknown as keyof typeof RecurrencyKindEnum;
|
||||
}
|
||||
|
||||
static fromChargingRateUnit(unit: string): keyof typeof ChargingRateUnitEnum {
|
||||
return unit as unknown as keyof typeof ChargingRateUnitEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an OCPP 1.6 SetChargingProfile csChargingProfiles to a native ChargingProfileInput.
|
||||
*/
|
||||
static fromSetChargingProfileRequest(
|
||||
profile: OCPP1_6.SetChargingProfileRequest['csChargingProfiles'],
|
||||
): ChargingProfileInput {
|
||||
return {
|
||||
id: profile.chargingProfileId,
|
||||
stackLevel: profile.stackLevel,
|
||||
chargingProfilePurpose: ChargingProfileMapper.fromChargingProfilePurpose(
|
||||
profile.chargingProfilePurpose,
|
||||
),
|
||||
chargingProfileKind: ChargingProfileMapper.fromChargingProfileKind(
|
||||
profile.chargingProfileKind,
|
||||
),
|
||||
recurrencyKind: ChargingProfileMapper.fromRecurrencyKind(profile.recurrencyKind),
|
||||
validFrom: profile.validFrom,
|
||||
validTo: profile.validTo,
|
||||
transactionId: profile.transactionId?.toString(),
|
||||
chargingSchedule: [
|
||||
ChargingProfileMapper.fromChargingSchedule(
|
||||
profile.chargingProfileId,
|
||||
profile.chargingSchedule,
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an OCPP 1.6 RemoteStartTransaction chargingProfile to a native ChargingProfileInput.
|
||||
*/
|
||||
static fromRemoteStartChargingProfile(
|
||||
profile: NonNullable<OCPP1_6.RemoteStartTransactionRequest['chargingProfile']>,
|
||||
): ChargingProfileInput {
|
||||
return {
|
||||
id: profile.chargingProfileId ?? 0,
|
||||
stackLevel: profile.stackLevel,
|
||||
chargingProfilePurpose: ChargingProfileMapper.fromChargingProfilePurpose(
|
||||
profile.chargingProfilePurpose,
|
||||
),
|
||||
chargingProfileKind: ChargingProfileMapper.fromChargingProfileKind(
|
||||
profile.chargingProfileKind,
|
||||
),
|
||||
recurrencyKind: ChargingProfileMapper.fromRecurrencyKind(profile.recurrencyKind),
|
||||
validFrom: profile.validFrom,
|
||||
validTo: profile.validTo,
|
||||
transactionId: profile.transactionId?.toString(),
|
||||
chargingSchedule: [
|
||||
ChargingProfileMapper.fromChargingSchedule(
|
||||
profile.chargingProfileId ?? 0,
|
||||
profile.chargingSchedule,
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an OCPP 1.6 ChargingSchedule to a native ChargingScheduleInput.
|
||||
* Accepts a scheduleId since OCPP 1.6 schedules don't have their own id.
|
||||
*/
|
||||
static fromChargingSchedule(
|
||||
scheduleId: number,
|
||||
schedule: {
|
||||
chargingRateUnit: string;
|
||||
chargingSchedulePeriod: {
|
||||
startPeriod: number;
|
||||
limit: number;
|
||||
numberPhases?: number | null;
|
||||
}[];
|
||||
duration?: number | null;
|
||||
startSchedule?: string | null;
|
||||
minChargingRate?: number | null;
|
||||
},
|
||||
): ChargingScheduleInput {
|
||||
return {
|
||||
id: scheduleId,
|
||||
chargingRateUnit: ChargingProfileMapper.fromChargingRateUnit(schedule.chargingRateUnit),
|
||||
chargingSchedulePeriod: schedule.chargingSchedulePeriod.map((period) => ({
|
||||
startPeriod: period.startPeriod,
|
||||
limit: period.limit,
|
||||
numberPhases: period.numberPhases,
|
||||
})) as [ChargingSchedulePeriodInput, ...ChargingSchedulePeriodInput[]],
|
||||
duration: schedule.duration,
|
||||
startSchedule: schedule.startSchedule,
|
||||
minChargingRate: schedule.minChargingRate,
|
||||
};
|
||||
}
|
||||
}
|
||||
76
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/LocationMapper.ts
vendored
Normal file
76
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/LocationMapper.ts
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ConnectorErrorCodeEnumType, ConnectorStatusEnumType } from '@citrineos/base';
|
||||
import { ConnectorErrorCodeEnum, ConnectorStatusEnum, OCPP1_6 } from '@citrineos/base';
|
||||
|
||||
export class LocationMapper {
|
||||
static mapStatusNotificationRequestStatusToConnectorStatus(
|
||||
status: OCPP1_6.StatusNotificationRequestStatus,
|
||||
): ConnectorStatusEnumType {
|
||||
switch (status) {
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Available:
|
||||
return ConnectorStatusEnum.Available;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Preparing:
|
||||
return ConnectorStatusEnum.Preparing;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Charging:
|
||||
return ConnectorStatusEnum.Charging;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.SuspendedEVSE:
|
||||
return ConnectorStatusEnum.SuspendedEVSE;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.SuspendedEV:
|
||||
return ConnectorStatusEnum.SuspendedEV;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Finishing:
|
||||
return ConnectorStatusEnum.Finishing;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Reserved:
|
||||
return ConnectorStatusEnum.Reserved;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Unavailable:
|
||||
return ConnectorStatusEnum.Unavailable;
|
||||
case OCPP1_6.StatusNotificationRequestStatus.Faulted:
|
||||
return ConnectorStatusEnum.Faulted;
|
||||
default:
|
||||
return ConnectorStatusEnum.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static mapStatusNotificationRequestErrorCodeToConnectorErrorCode(
|
||||
errorCode: OCPP1_6.StatusNotificationRequestErrorCode,
|
||||
): ConnectorErrorCodeEnumType {
|
||||
switch (errorCode) {
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.ConnectorLockFailure:
|
||||
return ConnectorErrorCodeEnum.ConnectorLockFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.EVCommunicationError:
|
||||
return ConnectorErrorCodeEnum.EVCommunicationError;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.GroundFailure:
|
||||
return ConnectorErrorCodeEnum.GroundFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.HighTemperature:
|
||||
return ConnectorErrorCodeEnum.HighTemperature;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.InternalError:
|
||||
return ConnectorErrorCodeEnum.InternalError;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.LocalListConflict:
|
||||
return ConnectorErrorCodeEnum.LocalListConflict;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.NoError:
|
||||
return ConnectorErrorCodeEnum.NoError;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.OtherError:
|
||||
return ConnectorErrorCodeEnum.OtherError;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.OverCurrentFailure:
|
||||
return ConnectorErrorCodeEnum.OverCurrentFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.PowerMeterFailure:
|
||||
return ConnectorErrorCodeEnum.PowerMeterFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.PowerSwitchFailure:
|
||||
return ConnectorErrorCodeEnum.PowerSwitchFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.ReaderFailure:
|
||||
return ConnectorErrorCodeEnum.ReaderFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.ResetFailure:
|
||||
return ConnectorErrorCodeEnum.ResetFailure;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.UnderVoltage:
|
||||
return ConnectorErrorCodeEnum.UnderVoltage;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.OverVoltage:
|
||||
return ConnectorErrorCodeEnum.OverVoltage;
|
||||
case OCPP1_6.StatusNotificationRequestErrorCode.WeakSignal:
|
||||
return ConnectorErrorCodeEnum.WeakSignal;
|
||||
default:
|
||||
throw new Error(`Unknown StatusNotificationRequestErrorCode: ${errorCode}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
488
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/MeterValueMapper.ts
vendored
Normal file
488
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/MeterValueMapper.ts
vendored
Normal file
@@ -0,0 +1,488 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
LocationEnum,
|
||||
MeasurandEnum,
|
||||
OCPP1_6,
|
||||
PhaseEnum,
|
||||
ReadingContextEnum,
|
||||
type MeterValueDto,
|
||||
type SampledValue,
|
||||
} from '@citrineos/base';
|
||||
|
||||
export class MeterValueMapper {
|
||||
/**
|
||||
* Converts native ReadingContextEnum to OCPP 1.6 MeterValuesRequestContext
|
||||
*/
|
||||
static toReadingContextEnumType(
|
||||
context?: keyof typeof ReadingContextEnum | null,
|
||||
): OCPP1_6.MeterValuesRequestContext | undefined {
|
||||
if (!context) return undefined;
|
||||
|
||||
switch (context) {
|
||||
case 'Interruption.Begin':
|
||||
return OCPP1_6.MeterValuesRequestContext.Interruption_Begin;
|
||||
case 'Interruption.End':
|
||||
return OCPP1_6.MeterValuesRequestContext.Interruption_End;
|
||||
case 'Other':
|
||||
return OCPP1_6.MeterValuesRequestContext.Other;
|
||||
case 'Sample.Clock':
|
||||
return OCPP1_6.MeterValuesRequestContext.Sample_Clock;
|
||||
case 'Sample.Periodic':
|
||||
return OCPP1_6.MeterValuesRequestContext.Sample_Periodic;
|
||||
case 'Transaction.Begin':
|
||||
return OCPP1_6.MeterValuesRequestContext.Transaction_Begin;
|
||||
case 'Transaction.End':
|
||||
return OCPP1_6.MeterValuesRequestContext.Transaction_End;
|
||||
case 'Trigger':
|
||||
return OCPP1_6.MeterValuesRequestContext.Trigger;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 MeterValuesRequestContext to native ReadingContextEnum
|
||||
*/
|
||||
static fromReadingContextEnumType(
|
||||
context?: OCPP1_6.MeterValuesRequestContext | null,
|
||||
): keyof typeof ReadingContextEnum | undefined {
|
||||
if (!context) return undefined;
|
||||
|
||||
switch (context) {
|
||||
case OCPP1_6.MeterValuesRequestContext.Interruption_Begin:
|
||||
return 'Interruption.Begin';
|
||||
case OCPP1_6.MeterValuesRequestContext.Interruption_End:
|
||||
return 'Interruption.End';
|
||||
case OCPP1_6.MeterValuesRequestContext.Other:
|
||||
return 'Other';
|
||||
case OCPP1_6.MeterValuesRequestContext.Sample_Clock:
|
||||
return 'Sample.Clock';
|
||||
case OCPP1_6.MeterValuesRequestContext.Sample_Periodic:
|
||||
return 'Sample.Periodic';
|
||||
case OCPP1_6.MeterValuesRequestContext.Transaction_Begin:
|
||||
return 'Transaction.Begin';
|
||||
case OCPP1_6.MeterValuesRequestContext.Transaction_End:
|
||||
return 'Transaction.End';
|
||||
case OCPP1_6.MeterValuesRequestContext.Trigger:
|
||||
return 'Trigger';
|
||||
default:
|
||||
return 'Sample.Periodic';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native MeasurandEnum to OCPP 1.6 MeterValuesRequestMeasurand
|
||||
*/
|
||||
static toMeasurandEnumType(
|
||||
measurand?: keyof typeof MeasurandEnum | null,
|
||||
): OCPP1_6.MeterValuesRequestMeasurand | undefined {
|
||||
if (!measurand) return undefined;
|
||||
|
||||
switch (measurand) {
|
||||
case 'Current.Export':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Current_Export;
|
||||
case 'Current.Import':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Current_Import;
|
||||
case 'Current.Offered':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Current_Offered;
|
||||
case 'Energy.Active.Export.Register':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Export_Register;
|
||||
case 'Energy.Active.Import.Register':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Import_Register;
|
||||
case 'Energy.Reactive.Export.Register':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Export_Register;
|
||||
case 'Energy.Reactive.Import.Register':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Import_Register;
|
||||
case 'Energy.Active.Export.Interval':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Export_Interval;
|
||||
case 'Energy.Active.Import.Interval':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Import_Interval;
|
||||
case 'Energy.Reactive.Export.Interval':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Export_Interval;
|
||||
case 'Energy.Reactive.Import.Interval':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Import_Interval;
|
||||
case 'Frequency':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Frequency;
|
||||
case 'Power.Active.Export':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Power_Active_Export;
|
||||
case 'Power.Active.Import':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Power_Active_Import;
|
||||
case 'Power.Factor':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Power_Factor;
|
||||
case 'Power.Offered':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Power_Offered;
|
||||
case 'Power.Reactive.Export':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Power_Reactive_Export;
|
||||
case 'Power.Reactive.Import':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Power_Reactive_Import;
|
||||
case 'SoC':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.SoC;
|
||||
case 'Voltage':
|
||||
return OCPP1_6.MeterValuesRequestMeasurand.Voltage;
|
||||
default:
|
||||
// Note: OCPP 2.0.1 measurands not supported in 1.6:
|
||||
// Energy.Active.Net, Energy.Reactive.Net, Energy.Apparent.Net,
|
||||
// Energy.Apparent.Import, Energy.Apparent.Export
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 MeterValuesRequestMeasurand to native MeasurandEnum
|
||||
*/
|
||||
static fromMeasurandEnumType(
|
||||
measurand?: OCPP1_6.MeterValuesRequestMeasurand | null,
|
||||
): keyof typeof MeasurandEnum | undefined {
|
||||
if (!measurand) return undefined;
|
||||
|
||||
switch (measurand) {
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Current_Export:
|
||||
return 'Current.Export';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Current_Import:
|
||||
return 'Current.Import';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Current_Offered:
|
||||
return 'Current.Offered';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Export_Register:
|
||||
return 'Energy.Active.Export.Register';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Import_Register:
|
||||
return 'Energy.Active.Import.Register';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Export_Register:
|
||||
return 'Energy.Reactive.Export.Register';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Import_Register:
|
||||
return 'Energy.Reactive.Import.Register';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Export_Interval:
|
||||
return 'Energy.Active.Export.Interval';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Active_Import_Interval:
|
||||
return 'Energy.Active.Import.Interval';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Export_Interval:
|
||||
return 'Energy.Reactive.Export.Interval';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Energy_Reactive_Import_Interval:
|
||||
return 'Energy.Reactive.Import.Interval';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Frequency:
|
||||
return 'Frequency';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Power_Active_Export:
|
||||
return 'Power.Active.Export';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Power_Active_Import:
|
||||
return 'Power.Active.Import';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Power_Factor:
|
||||
return 'Power.Factor';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Power_Offered:
|
||||
return 'Power.Offered';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Power_Reactive_Export:
|
||||
return 'Power.Reactive.Export';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Power_Reactive_Import:
|
||||
return 'Power.Reactive.Import';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.RPM:
|
||||
return 'RPM';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.SoC:
|
||||
return 'SoC';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Temperature:
|
||||
return 'Temperature';
|
||||
case OCPP1_6.MeterValuesRequestMeasurand.Voltage:
|
||||
return 'Voltage';
|
||||
default:
|
||||
return 'Energy.Active.Import.Register';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native LocationEnum to OCPP 1.6 MeterValuesRequestLocation
|
||||
*/
|
||||
static toLocationEnumType(
|
||||
location?: keyof typeof LocationEnum | null,
|
||||
): OCPP1_6.MeterValuesRequestLocation | undefined {
|
||||
if (!location) return undefined;
|
||||
|
||||
switch (location) {
|
||||
case 'Body':
|
||||
return OCPP1_6.MeterValuesRequestLocation.Body;
|
||||
case 'Cable':
|
||||
return OCPP1_6.MeterValuesRequestLocation.Cable;
|
||||
case 'EV':
|
||||
return OCPP1_6.MeterValuesRequestLocation.EV;
|
||||
case 'Inlet':
|
||||
return OCPP1_6.MeterValuesRequestLocation.Inlet;
|
||||
case 'Outlet':
|
||||
return OCPP1_6.MeterValuesRequestLocation.Outlet;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 MeterValuesRequestLocation to native LocationEnum
|
||||
*/
|
||||
static fromLocationEnumType(
|
||||
location?: OCPP1_6.MeterValuesRequestLocation | null,
|
||||
): keyof typeof LocationEnum | undefined {
|
||||
if (!location) return undefined;
|
||||
|
||||
switch (location) {
|
||||
case OCPP1_6.MeterValuesRequestLocation.Body:
|
||||
return 'Body';
|
||||
case OCPP1_6.MeterValuesRequestLocation.Cable:
|
||||
return 'Cable';
|
||||
case OCPP1_6.MeterValuesRequestLocation.EV:
|
||||
return 'EV';
|
||||
case OCPP1_6.MeterValuesRequestLocation.Inlet:
|
||||
return 'Inlet';
|
||||
case OCPP1_6.MeterValuesRequestLocation.Outlet:
|
||||
return 'Outlet';
|
||||
default:
|
||||
return 'Outlet';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native PhaseEnum to OCPP 1.6 MeterValuesRequestPhase
|
||||
*/
|
||||
static toPhaseEnumType(
|
||||
phase?: keyof typeof PhaseEnum | null,
|
||||
): OCPP1_6.MeterValuesRequestPhase | undefined {
|
||||
if (!phase) return undefined;
|
||||
|
||||
switch (phase) {
|
||||
case 'L1':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L1;
|
||||
case 'L2':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L2;
|
||||
case 'L3':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L3;
|
||||
case 'N':
|
||||
return OCPP1_6.MeterValuesRequestPhase.N;
|
||||
case 'L1-N':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L1_N;
|
||||
case 'L2-N':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L2_N;
|
||||
case 'L3-N':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L3_N;
|
||||
case 'L1-L2':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L1_L2;
|
||||
case 'L2-L3':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L2_L3;
|
||||
case 'L3-L1':
|
||||
return OCPP1_6.MeterValuesRequestPhase.L3_L1;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 MeterValuesRequestPhase to native PhaseEnum
|
||||
*/
|
||||
static fromPhaseEnumType(
|
||||
phase?: OCPP1_6.MeterValuesRequestPhase | null,
|
||||
): keyof typeof PhaseEnum | undefined {
|
||||
if (!phase) return undefined;
|
||||
|
||||
switch (phase) {
|
||||
case OCPP1_6.MeterValuesRequestPhase.L1:
|
||||
return 'L1';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L2:
|
||||
return 'L2';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L3:
|
||||
return 'L3';
|
||||
case OCPP1_6.MeterValuesRequestPhase.N:
|
||||
return 'N';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L1_N:
|
||||
return 'L1-N';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L2_N:
|
||||
return 'L2-N';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L3_N:
|
||||
return 'L3-N';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L1_L2:
|
||||
return 'L1-L2';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L2_L3:
|
||||
return 'L2-L3';
|
||||
case OCPP1_6.MeterValuesRequestPhase.L3_L1:
|
||||
return 'L3-L1';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native UnitOfMeasure to OCPP 1.6 MeterValuesRequestUnit
|
||||
*/
|
||||
static toUnitEnumType(unit?: string | null): OCPP1_6.MeterValuesRequestUnit | undefined {
|
||||
if (!unit) return undefined;
|
||||
|
||||
switch (unit) {
|
||||
case 'Wh':
|
||||
return OCPP1_6.MeterValuesRequestUnit.Wh;
|
||||
case 'kWh':
|
||||
return OCPP1_6.MeterValuesRequestUnit.kWh;
|
||||
case 'varh':
|
||||
return OCPP1_6.MeterValuesRequestUnit.varh;
|
||||
case 'kvarh':
|
||||
return OCPP1_6.MeterValuesRequestUnit.kvarh;
|
||||
case 'W':
|
||||
return OCPP1_6.MeterValuesRequestUnit.W;
|
||||
case 'kW':
|
||||
return OCPP1_6.MeterValuesRequestUnit.kW;
|
||||
case 'VA':
|
||||
return OCPP1_6.MeterValuesRequestUnit.VA;
|
||||
case 'kVA':
|
||||
return OCPP1_6.MeterValuesRequestUnit.kVA;
|
||||
case 'var':
|
||||
return OCPP1_6.MeterValuesRequestUnit.var;
|
||||
case 'kvar':
|
||||
return OCPP1_6.MeterValuesRequestUnit.kvar;
|
||||
case 'A':
|
||||
return OCPP1_6.MeterValuesRequestUnit.A;
|
||||
case 'V':
|
||||
return OCPP1_6.MeterValuesRequestUnit.V;
|
||||
case 'K':
|
||||
return OCPP1_6.MeterValuesRequestUnit.K;
|
||||
case 'Celsius':
|
||||
return OCPP1_6.MeterValuesRequestUnit.Celsius;
|
||||
case 'Fahrenheit':
|
||||
return OCPP1_6.MeterValuesRequestUnit.Fahrenheit;
|
||||
case 'Percent':
|
||||
return OCPP1_6.MeterValuesRequestUnit.Percent;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 MeterValuesRequestUnit to native unit string
|
||||
*/
|
||||
static fromUnitEnumType(unit?: OCPP1_6.MeterValuesRequestUnit | null): string | undefined {
|
||||
if (!unit) return undefined;
|
||||
|
||||
switch (unit) {
|
||||
case OCPP1_6.MeterValuesRequestUnit.Wh:
|
||||
return 'Wh';
|
||||
case OCPP1_6.MeterValuesRequestUnit.kWh:
|
||||
return 'kWh';
|
||||
case OCPP1_6.MeterValuesRequestUnit.varh:
|
||||
return 'varh';
|
||||
case OCPP1_6.MeterValuesRequestUnit.kvarh:
|
||||
return 'kvarh';
|
||||
case OCPP1_6.MeterValuesRequestUnit.W:
|
||||
return 'W';
|
||||
case OCPP1_6.MeterValuesRequestUnit.kW:
|
||||
return 'kW';
|
||||
case OCPP1_6.MeterValuesRequestUnit.VA:
|
||||
return 'VA';
|
||||
case OCPP1_6.MeterValuesRequestUnit.kVA:
|
||||
return 'kVA';
|
||||
case OCPP1_6.MeterValuesRequestUnit.var:
|
||||
return 'var';
|
||||
case OCPP1_6.MeterValuesRequestUnit.kvar:
|
||||
return 'kvar';
|
||||
case OCPP1_6.MeterValuesRequestUnit.A:
|
||||
return 'A';
|
||||
case OCPP1_6.MeterValuesRequestUnit.V:
|
||||
return 'V';
|
||||
case OCPP1_6.MeterValuesRequestUnit.K:
|
||||
return 'K';
|
||||
case OCPP1_6.MeterValuesRequestUnit.Celcius:
|
||||
case OCPP1_6.MeterValuesRequestUnit.Celsius:
|
||||
return 'Celsius';
|
||||
case OCPP1_6.MeterValuesRequestUnit.Fahrenheit:
|
||||
return 'Fahrenheit';
|
||||
case OCPP1_6.MeterValuesRequestUnit.Percent:
|
||||
return 'Percent';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OCPP 1.6 SampledValue type (inline from MeterValuesRequest)
|
||||
*/
|
||||
static toSampledValueType(
|
||||
sampledValue: SampledValue,
|
||||
): OCPP1_6.MeterValuesRequest['meterValue'][0]['sampledValue'][0] {
|
||||
return {
|
||||
value: String(sampledValue.value * 10 ** (sampledValue.unitOfMeasure?.multiplier ?? 0)),
|
||||
context: MeterValueMapper.toReadingContextEnumType(sampledValue.context),
|
||||
measurand: MeterValueMapper.toMeasurandEnumType(sampledValue.measurand),
|
||||
phase: MeterValueMapper.toPhaseEnumType(sampledValue.phase),
|
||||
location: MeterValueMapper.toLocationEnumType(sampledValue.location),
|
||||
unit: MeterValueMapper.toUnitEnumType(sampledValue.unitOfMeasure?.unit),
|
||||
// Note: no support for OCPP 1.6 signedMeterValues
|
||||
};
|
||||
}
|
||||
|
||||
static toMeterValueType(meterValue: MeterValueDto): OCPP1_6.MeterValuesRequest['meterValue'][0] {
|
||||
return {
|
||||
timestamp: meterValue.timestamp,
|
||||
sampledValue: MeterValueMapper.toSampledValueTypes(meterValue.sampledValue),
|
||||
};
|
||||
}
|
||||
|
||||
static toSampledValueTypes(
|
||||
sampledValues: SampledValue[],
|
||||
): OCPP1_6.MeterValuesRequest['meterValue'][0]['sampledValue'] {
|
||||
return sampledValues.map((sv) => MeterValueMapper.toSampledValueType(sv));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the format field for OCPP 1.6 sampledValue.
|
||||
*/
|
||||
static validateFormat(format?: OCPP1_6.MeterValuesRequestFormat | null): boolean {
|
||||
return format === undefined || format === OCPP1_6.MeterValuesRequestFormat.Raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 sampledValue to native SampledValue
|
||||
*/
|
||||
static fromSampledValueType(
|
||||
sampledValueType: OCPP1_6.MeterValuesRequest['meterValue'][0]['sampledValue'][0],
|
||||
): SampledValue | undefined {
|
||||
// Validate format - return undefined if not Raw or undefined
|
||||
if (!MeterValueMapper.validateFormat(sampledValueType.format)) {
|
||||
console.warn(`Unsupported OCPP 1.6 sampledValue format: ${sampledValueType.format}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sampledValue: SampledValue = {
|
||||
value: parseFloat(sampledValueType.value),
|
||||
context: MeterValueMapper.fromReadingContextEnumType(sampledValueType.context),
|
||||
measurand: MeterValueMapper.fromMeasurandEnumType(sampledValueType.measurand),
|
||||
phase: MeterValueMapper.fromPhaseEnumType(sampledValueType.phase),
|
||||
location: MeterValueMapper.fromLocationEnumType(sampledValueType.location),
|
||||
};
|
||||
|
||||
const unit = MeterValueMapper.fromUnitEnumType(sampledValueType.unit);
|
||||
if (unit) {
|
||||
sampledValue.unitOfMeasure = {
|
||||
unit,
|
||||
multiplier: 0, // OCPP 1.6 does not have multiplier
|
||||
};
|
||||
}
|
||||
|
||||
return sampledValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 SampledValueType[] back to SampledValue[]
|
||||
*/
|
||||
static fromSampledValueTypes(
|
||||
sampledValueTypes: OCPP1_6.MeterValuesRequest['meterValue'][0]['sampledValue'],
|
||||
): [SampledValue, ...SampledValue[]] {
|
||||
const sampledValues = sampledValueTypes.map((svt) =>
|
||||
MeterValueMapper.fromSampledValueType(svt),
|
||||
);
|
||||
|
||||
return sampledValues as [SampledValue, ...SampledValue[]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 1.6 MeterValueType back to a partial MeterValue structure
|
||||
*/
|
||||
static fromMeterValueType(
|
||||
meterValueType: OCPP1_6.MeterValuesRequest['meterValue'][0],
|
||||
): MeterValueDto {
|
||||
return {
|
||||
timestamp: meterValueType.timestamp,
|
||||
sampledValue: MeterValueMapper.fromSampledValueTypes(meterValueType.sampledValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
9
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/index.ts
vendored
Normal file
9
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/1.6/index.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { BootMapper } from './BootMapper.js';
|
||||
export { AuthorizationMapper } from './AuthorizationMapper.js';
|
||||
export { LocationMapper } from './LocationMapper.js';
|
||||
export { MeterValueMapper } from './MeterValueMapper.js';
|
||||
export { ChargingProfileMapper } from './ChargingProfileMapper.js';
|
||||
168
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/AuthorizationMapper.ts
vendored
Normal file
168
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/AuthorizationMapper.ts
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { AuthorizationStatusEnumType, IdTokenEnumType } from '@citrineos/base';
|
||||
import { AuthorizationStatusEnum, IdTokenEnum, OCPP2_0_1 } from '@citrineos/base';
|
||||
import { Authorization } from '../../model/Authorization/Authorization.js';
|
||||
export class AuthorizationMapper {
|
||||
static toAuthorizationData(authorization: Authorization): OCPP2_0_1.AuthorizationData {
|
||||
return {
|
||||
customData: authorization.customData,
|
||||
idToken: AuthorizationMapper.toIdToken(authorization),
|
||||
idTokenInfo: AuthorizationMapper.toIdTokenInfo(authorization),
|
||||
};
|
||||
}
|
||||
|
||||
static toIdToken(authorization: Authorization): OCPP2_0_1.IdTokenType {
|
||||
if (!authorization.idTokenType) {
|
||||
throw new Error('IdToken type is missing.');
|
||||
}
|
||||
return {
|
||||
customData: authorization.customData,
|
||||
additionalInfo: authorization.additionalInfo ?? null,
|
||||
idToken: authorization.idToken,
|
||||
type: AuthorizationMapper.toIdTokenEnumType(authorization.idTokenType),
|
||||
};
|
||||
}
|
||||
|
||||
static toIdTokenInfo(authorization: Authorization): OCPP2_0_1.IdTokenInfoType {
|
||||
return {
|
||||
status: AuthorizationMapper.fromAuthorizationStatusEnumType(authorization.status),
|
||||
cacheExpiryDateTime: authorization.cacheExpiryDateTime,
|
||||
chargingPriority: authorization.chargingPriority,
|
||||
language1: authorization.language1,
|
||||
language2: authorization.language2,
|
||||
personalMessage: authorization.personalMessage,
|
||||
customData: authorization.customData,
|
||||
};
|
||||
}
|
||||
|
||||
static toMessageContentType(messageContent: any): OCPP2_0_1.MessageContentType {
|
||||
return {
|
||||
customData: messageContent.customData,
|
||||
format: AuthorizationMapper.toMessageFormatEnum(messageContent.format),
|
||||
language: messageContent.language,
|
||||
content: messageContent.content,
|
||||
};
|
||||
}
|
||||
|
||||
static toMessageFormatEnum(messageFormat: string): OCPP2_0_1.MessageFormatEnumType {
|
||||
switch (messageFormat) {
|
||||
case 'ASCII':
|
||||
return OCPP2_0_1.MessageFormatEnumType.ASCII;
|
||||
case 'HTML':
|
||||
return OCPP2_0_1.MessageFormatEnumType.HTML;
|
||||
case 'URI':
|
||||
return OCPP2_0_1.MessageFormatEnumType.URI;
|
||||
case 'UTF8':
|
||||
return OCPP2_0_1.MessageFormatEnumType.UTF8;
|
||||
default:
|
||||
throw new Error('Unknown message format');
|
||||
}
|
||||
}
|
||||
|
||||
static fromAuthorizationStatusEnumType(
|
||||
status: AuthorizationStatusEnumType,
|
||||
): OCPP2_0_1.AuthorizationStatusEnumType {
|
||||
switch (status) {
|
||||
case AuthorizationStatusEnum.Accepted:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.Accepted;
|
||||
case AuthorizationStatusEnum.Blocked:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.Blocked;
|
||||
case AuthorizationStatusEnum.ConcurrentTx:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.ConcurrentTx;
|
||||
case AuthorizationStatusEnum.Expired:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.Expired;
|
||||
case AuthorizationStatusEnum.Invalid:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.Invalid;
|
||||
case AuthorizationStatusEnum.NoCredit:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.NoCredit;
|
||||
case AuthorizationStatusEnum.NotAllowedTypeEVSE:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.NotAllowedTypeEVSE;
|
||||
case AuthorizationStatusEnum.NotAtThisLocation:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.NotAtThisLocation;
|
||||
case AuthorizationStatusEnum.NotAtThisTime:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.NotAtThisTime;
|
||||
case AuthorizationStatusEnum.Unknown:
|
||||
return OCPP2_0_1.AuthorizationStatusEnumType.Unknown;
|
||||
default:
|
||||
throw new Error('Unknown authorization status: ' + status);
|
||||
}
|
||||
}
|
||||
|
||||
static toAuthorizationStatusEnumType(
|
||||
status: OCPP2_0_1.AuthorizationStatusEnumType,
|
||||
): AuthorizationStatusEnumType {
|
||||
switch (status) {
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.Accepted:
|
||||
return AuthorizationStatusEnum.Accepted;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.Blocked:
|
||||
return AuthorizationStatusEnum.Blocked;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.ConcurrentTx:
|
||||
return AuthorizationStatusEnum.ConcurrentTx;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.Expired:
|
||||
return AuthorizationStatusEnum.Expired;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.Invalid:
|
||||
return AuthorizationStatusEnum.Invalid;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.NoCredit:
|
||||
return AuthorizationStatusEnum.NoCredit;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.NotAllowedTypeEVSE:
|
||||
return AuthorizationStatusEnum.NotAllowedTypeEVSE;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.NotAtThisLocation:
|
||||
return AuthorizationStatusEnum.NotAtThisLocation;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.NotAtThisTime:
|
||||
return AuthorizationStatusEnum.NotAtThisTime;
|
||||
case OCPP2_0_1.AuthorizationStatusEnumType.Unknown:
|
||||
return AuthorizationStatusEnum.Unknown;
|
||||
default:
|
||||
throw new Error('Unknown authorization status');
|
||||
}
|
||||
}
|
||||
|
||||
static toIdTokenEnumType(type: IdTokenEnumType): OCPP2_0_1.IdTokenEnumType {
|
||||
switch (type) {
|
||||
case IdTokenEnum.Central:
|
||||
case IdTokenEnum.Other:
|
||||
return OCPP2_0_1.IdTokenEnumType.Central;
|
||||
case IdTokenEnum.eMAID:
|
||||
return OCPP2_0_1.IdTokenEnumType.eMAID;
|
||||
case IdTokenEnum.ISO14443:
|
||||
return OCPP2_0_1.IdTokenEnumType.ISO14443;
|
||||
case IdTokenEnum.ISO15693:
|
||||
return OCPP2_0_1.IdTokenEnumType.ISO15693;
|
||||
case IdTokenEnum.KeyCode:
|
||||
return OCPP2_0_1.IdTokenEnumType.KeyCode;
|
||||
case IdTokenEnum.Local:
|
||||
return OCPP2_0_1.IdTokenEnumType.Local;
|
||||
case IdTokenEnum.MacAddress:
|
||||
return OCPP2_0_1.IdTokenEnumType.MacAddress;
|
||||
case IdTokenEnum.NoAuthorization:
|
||||
return OCPP2_0_1.IdTokenEnumType.NoAuthorization;
|
||||
default:
|
||||
throw new Error(`Unknown idToken type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
static fromIdTokenEnumType(type: OCPP2_0_1.IdTokenEnumType): IdTokenEnumType {
|
||||
switch (type) {
|
||||
case OCPP2_0_1.IdTokenEnumType.Central:
|
||||
return IdTokenEnum.Central;
|
||||
case OCPP2_0_1.IdTokenEnumType.eMAID:
|
||||
return IdTokenEnum.eMAID;
|
||||
case OCPP2_0_1.IdTokenEnumType.ISO14443:
|
||||
return IdTokenEnum.ISO14443;
|
||||
case OCPP2_0_1.IdTokenEnumType.ISO15693:
|
||||
return IdTokenEnum.ISO15693;
|
||||
case OCPP2_0_1.IdTokenEnumType.KeyCode:
|
||||
return IdTokenEnum.KeyCode;
|
||||
case OCPP2_0_1.IdTokenEnumType.Local:
|
||||
return IdTokenEnum.Local;
|
||||
case OCPP2_0_1.IdTokenEnumType.MacAddress:
|
||||
return IdTokenEnum.MacAddress;
|
||||
case OCPP2_0_1.IdTokenEnumType.NoAuthorization:
|
||||
return IdTokenEnum.NoAuthorization;
|
||||
default:
|
||||
throw new Error(`Unknown OCPP 2.0.1 idToken type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/BootMapper.ts
vendored
Normal file
31
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/BootMapper.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { OCPP2_0_1 } from '@citrineos/base';
|
||||
|
||||
export class BootMapper {
|
||||
static toRegistrationStatusEnumType(status: string): OCPP2_0_1.RegistrationStatusEnumType {
|
||||
switch (status) {
|
||||
case 'Accepted':
|
||||
return OCPP2_0_1.RegistrationStatusEnumType.Accepted;
|
||||
case 'Pending':
|
||||
return OCPP2_0_1.RegistrationStatusEnumType.Pending;
|
||||
case 'Rejected':
|
||||
return OCPP2_0_1.RegistrationStatusEnumType.Rejected;
|
||||
default:
|
||||
throw new Error(`Invalid status: ${status}`);
|
||||
}
|
||||
}
|
||||
|
||||
static toStatusInfo(statusInfo?: any): any {
|
||||
if (!statusInfo) {
|
||||
return statusInfo;
|
||||
}
|
||||
return {
|
||||
customData: statusInfo.customData,
|
||||
reasonCode: statusInfo.reasonCode,
|
||||
additionalInfo: statusInfo.additionalInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
ChargingLimitSourceEnum,
|
||||
ChargingProfileKindEnum,
|
||||
ChargingProfilePurposeEnum,
|
||||
ChargingRateUnitEnum,
|
||||
OCPP2_0_1,
|
||||
OCPP2_1,
|
||||
OCPP2_common_types,
|
||||
RecurrencyKindEnum,
|
||||
type ChargingProfileDto,
|
||||
type ChargingScheduleDto,
|
||||
} from '@citrineos/base';
|
||||
|
||||
/**
|
||||
* Input type for creating/updating a ChargingProfile via the repository.
|
||||
* Mirrors OCPP2_0_1.ChargingProfileType but uses native enum types.
|
||||
*/
|
||||
export interface ChargingProfileInput {
|
||||
id: number;
|
||||
stackLevel: number;
|
||||
chargingProfilePurpose: keyof typeof ChargingProfilePurposeEnum;
|
||||
chargingProfileKind: keyof typeof ChargingProfileKindEnum;
|
||||
recurrencyKind?: keyof typeof RecurrencyKindEnum | null;
|
||||
validFrom?: string | null;
|
||||
validTo?: string | null;
|
||||
chargingSchedule:
|
||||
| [ChargingScheduleInput]
|
||||
| [ChargingScheduleInput, ChargingScheduleInput]
|
||||
| [ChargingScheduleInput, ChargingScheduleInput, ChargingScheduleInput];
|
||||
transactionId?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input type for creating a ChargingSchedule via the repository.
|
||||
* Mirrors OCPP2_0_1.ChargingScheduleType but uses native enum types.
|
||||
*/
|
||||
export interface ChargingScheduleInput {
|
||||
id: number;
|
||||
startSchedule?: string | null;
|
||||
duration?: number | null;
|
||||
chargingRateUnit: keyof typeof ChargingRateUnitEnum;
|
||||
chargingSchedulePeriod: [ChargingSchedulePeriodInput, ...ChargingSchedulePeriodInput[]];
|
||||
minChargingRate?: number | null;
|
||||
salesTariff?: SalesTariffInput | null;
|
||||
}
|
||||
|
||||
export interface ChargingSchedulePeriodInput {
|
||||
startPeriod: number;
|
||||
limit: number;
|
||||
numberPhases?: number | null;
|
||||
phaseToUse?: number | null;
|
||||
}
|
||||
|
||||
export interface SalesTariffInput {
|
||||
id: number;
|
||||
salesTariffDescription?: string | null;
|
||||
numEPriceLevels?: number | null;
|
||||
salesTariffEntry: [OCPP2_0_1.SalesTariffEntryType, ...OCPP2_0_1.SalesTariffEntryType[]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Input type for creating a CompositeSchedule via the repository.
|
||||
* Mirrors OCPP2_0_1.CompositeScheduleType but uses native enum types.
|
||||
*/
|
||||
export interface CompositeScheduleInput {
|
||||
chargingSchedulePeriod: [ChargingSchedulePeriodInput, ...ChargingSchedulePeriodInput[]];
|
||||
evseId: number;
|
||||
duration: number;
|
||||
scheduleStart: string;
|
||||
chargingRateUnit: keyof typeof ChargingRateUnitEnum;
|
||||
}
|
||||
|
||||
export class ChargingProfileMapper {
|
||||
// =========================================================================
|
||||
// Enum converters: Native → OCPP 2.0.1
|
||||
// Note: Native enum values are identical to OCPP 2.0.1 enum values,
|
||||
// so these are type-safe casts rather than value transformations.
|
||||
// =========================================================================
|
||||
|
||||
static toChargingProfileKindEnumType(
|
||||
kind: keyof typeof ChargingProfileKindEnum,
|
||||
): OCPP2_0_1.ChargingProfileKindEnumType {
|
||||
return kind as unknown as OCPP2_0_1.ChargingProfileKindEnumType;
|
||||
}
|
||||
|
||||
static fromChargingProfileKindEnumType(
|
||||
kind: OCPP2_1.ChargingProfileKindEnumType,
|
||||
): keyof typeof ChargingProfileKindEnum {
|
||||
return kind as unknown as keyof typeof ChargingProfileKindEnum;
|
||||
}
|
||||
|
||||
static toChargingProfilePurposeEnumType(
|
||||
purpose: keyof typeof ChargingProfilePurposeEnum,
|
||||
): OCPP2_0_1.ChargingProfilePurposeEnumType {
|
||||
return purpose as unknown as OCPP2_0_1.ChargingProfilePurposeEnumType;
|
||||
}
|
||||
|
||||
static fromChargingProfilePurposeEnumType(
|
||||
purpose: OCPP2_1.ChargingProfilePurposeEnumType,
|
||||
): keyof typeof ChargingProfilePurposeEnum {
|
||||
return purpose as unknown as keyof typeof ChargingProfilePurposeEnum;
|
||||
}
|
||||
|
||||
static toRecurrencyKindEnumType(
|
||||
kind?: keyof typeof RecurrencyKindEnum | null,
|
||||
): OCPP2_0_1.RecurrencyKindEnumType | undefined {
|
||||
if (!kind) return undefined;
|
||||
return kind as unknown as OCPP2_0_1.RecurrencyKindEnumType;
|
||||
}
|
||||
|
||||
static fromRecurrencyKindEnumType(
|
||||
kind?: OCPP2_0_1.RecurrencyKindEnumType | null,
|
||||
): keyof typeof RecurrencyKindEnum | undefined {
|
||||
if (!kind) return undefined;
|
||||
return kind as unknown as keyof typeof RecurrencyKindEnum;
|
||||
}
|
||||
|
||||
static toChargingRateUnitEnumType(
|
||||
unit: keyof typeof ChargingRateUnitEnum,
|
||||
): OCPP2_0_1.ChargingRateUnitEnumType {
|
||||
return unit as unknown as OCPP2_0_1.ChargingRateUnitEnumType;
|
||||
}
|
||||
|
||||
static fromChargingRateUnitEnumType(
|
||||
unit: OCPP2_0_1.ChargingRateUnitEnumType,
|
||||
): keyof typeof ChargingRateUnitEnum {
|
||||
return unit as unknown as keyof typeof ChargingRateUnitEnum;
|
||||
}
|
||||
|
||||
static toChargingLimitSourceEnumType(
|
||||
source?: keyof typeof ChargingLimitSourceEnum | null,
|
||||
): OCPP2_0_1.ChargingLimitSourceEnumType | undefined {
|
||||
if (!source) return undefined;
|
||||
return source as unknown as OCPP2_0_1.ChargingLimitSourceEnumType;
|
||||
}
|
||||
|
||||
static fromChargingLimitSourceEnumType(
|
||||
source?: OCPP2_1.ChargingLimitSourceEnumType | null,
|
||||
): keyof typeof ChargingLimitSourceEnum | undefined {
|
||||
if (!source) return undefined;
|
||||
return source as unknown as keyof typeof ChargingLimitSourceEnum;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Object converters: OCPP 2.0.1 → Native
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Converts OCPP2_0_1.ChargingProfileType to a native ChargingProfileInput.
|
||||
*/
|
||||
static fromChargingProfileType(
|
||||
chargingProfile: OCPP2_0_1.ChargingProfileType | OCPP2_1.ChargingProfileType,
|
||||
): ChargingProfileInput {
|
||||
return {
|
||||
id: chargingProfile.id,
|
||||
stackLevel: chargingProfile.stackLevel,
|
||||
chargingProfilePurpose: ChargingProfileMapper.fromChargingProfilePurposeEnumType(
|
||||
chargingProfile.chargingProfilePurpose,
|
||||
),
|
||||
chargingProfileKind: ChargingProfileMapper.fromChargingProfileKindEnumType(
|
||||
chargingProfile.chargingProfileKind,
|
||||
),
|
||||
recurrencyKind: ChargingProfileMapper.fromRecurrencyKindEnumType(
|
||||
chargingProfile.recurrencyKind,
|
||||
),
|
||||
validFrom: chargingProfile.validFrom,
|
||||
validTo: chargingProfile.validTo,
|
||||
chargingSchedule: chargingProfile.chargingSchedule.map((schedule) =>
|
||||
ChargingProfileMapper.fromChargingScheduleType(schedule),
|
||||
) as ChargingProfileInput['chargingSchedule'],
|
||||
transactionId: chargingProfile.transactionId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP2_0_1.ChargingScheduleType to a native ChargingScheduleInput.
|
||||
*/
|
||||
static fromChargingScheduleType(schedule: OCPP2_1.ChargingScheduleType): ChargingScheduleInput {
|
||||
return {
|
||||
id: schedule.id,
|
||||
startSchedule: schedule.startSchedule,
|
||||
duration: schedule.duration,
|
||||
chargingRateUnit: ChargingProfileMapper.fromChargingRateUnitEnumType(
|
||||
schedule.chargingRateUnit,
|
||||
),
|
||||
chargingSchedulePeriod: schedule.chargingSchedulePeriod.map((period) => ({
|
||||
startPeriod: period.startPeriod,
|
||||
limit: period.limit,
|
||||
numberPhases: period.numberPhases,
|
||||
phaseToUse: period.phaseToUse,
|
||||
})) as [ChargingSchedulePeriodInput, ...ChargingSchedulePeriodInput[]],
|
||||
minChargingRate: schedule.minChargingRate,
|
||||
salesTariff: schedule.salesTariff
|
||||
? {
|
||||
id: schedule.salesTariff.id,
|
||||
salesTariffDescription: schedule.salesTariff.salesTariffDescription,
|
||||
numEPriceLevels: schedule.salesTariff.numEPriceLevels,
|
||||
salesTariffEntry: schedule.salesTariff.salesTariffEntry,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP2_0_1.CompositeScheduleType to a native CompositeScheduleInput.
|
||||
*/
|
||||
static fromCompositeScheduleType(
|
||||
compositeSchedule: OCPP2_common_types.CompositeScheduleType,
|
||||
): CompositeScheduleInput {
|
||||
return {
|
||||
chargingSchedulePeriod: compositeSchedule.chargingSchedulePeriod.map((period) => ({
|
||||
startPeriod: period.startPeriod,
|
||||
limit: period.limit,
|
||||
numberPhases: period.numberPhases,
|
||||
phaseToUse: period.phaseToUse,
|
||||
})) as [ChargingSchedulePeriodInput, ...ChargingSchedulePeriodInput[]],
|
||||
evseId: compositeSchedule.evseId,
|
||||
duration: compositeSchedule.duration,
|
||||
scheduleStart: compositeSchedule.scheduleStart,
|
||||
chargingRateUnit: ChargingProfileMapper.fromChargingRateUnitEnumType(
|
||||
compositeSchedule.chargingRateUnit,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Object converters: Native → OCPP 2.0.1
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Converts a native ChargingProfile (Sequelize model) to OCPP2_0_1.ChargingProfileType.
|
||||
*/
|
||||
static toChargingProfileType(
|
||||
chargingProfile: ChargingProfileDto,
|
||||
transactionId?: string | null,
|
||||
): OCPP2_0_1.ChargingProfileType {
|
||||
return {
|
||||
id: chargingProfile.id!,
|
||||
stackLevel: chargingProfile.stackLevel,
|
||||
chargingProfilePurpose: ChargingProfileMapper.toChargingProfilePurposeEnumType(
|
||||
chargingProfile.chargingProfilePurpose,
|
||||
),
|
||||
chargingProfileKind: ChargingProfileMapper.toChargingProfileKindEnumType(
|
||||
chargingProfile.chargingProfileKind,
|
||||
),
|
||||
recurrencyKind: ChargingProfileMapper.toRecurrencyKindEnumType(
|
||||
chargingProfile.recurrencyKind,
|
||||
),
|
||||
validFrom: chargingProfile.validFrom,
|
||||
validTo: chargingProfile.validTo,
|
||||
chargingSchedule: chargingProfile.chargingSchedule.map((schedule) =>
|
||||
ChargingProfileMapper.toChargingScheduleType(schedule),
|
||||
) as OCPP2_0_1.ChargingProfileType['chargingSchedule'],
|
||||
transactionId: transactionId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a native ChargingScheduleDto to OCPP2_0_1.ChargingScheduleType.
|
||||
*/
|
||||
static toChargingScheduleType(schedule: ChargingScheduleDto): OCPP2_0_1.ChargingScheduleType {
|
||||
return {
|
||||
id: schedule.id!,
|
||||
startSchedule: schedule.startSchedule,
|
||||
duration: schedule.duration,
|
||||
chargingRateUnit: ChargingProfileMapper.toChargingRateUnitEnumType(schedule.chargingRateUnit),
|
||||
chargingSchedulePeriod:
|
||||
schedule.chargingSchedulePeriod as OCPP2_0_1.ChargingScheduleType['chargingSchedulePeriod'],
|
||||
minChargingRate: schedule.minChargingRate,
|
||||
salesTariff: schedule.salesTariff
|
||||
? {
|
||||
id: schedule.salesTariff.id!,
|
||||
salesTariffDescription: schedule.salesTariff.salesTariffDescription,
|
||||
numEPriceLevels: schedule.salesTariff.numEPriceLevels,
|
||||
salesTariffEntry: schedule.salesTariff.salesTariffEntry as [
|
||||
OCPP2_0_1.SalesTariffEntryType,
|
||||
...OCPP2_0_1.SalesTariffEntryType[],
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
25
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/LocationMapper.ts
vendored
Normal file
25
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/LocationMapper.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ConnectorStatusEnumType } from '@citrineos/base';
|
||||
import { ConnectorStatusEnum, OCPP2_0_1 } from '@citrineos/base';
|
||||
|
||||
export class LocationMapper {
|
||||
static mapConnectorStatus(status: OCPP2_0_1.ConnectorStatusEnumType): ConnectorStatusEnumType {
|
||||
switch (status) {
|
||||
case OCPP2_0_1.ConnectorStatusEnumType.Available:
|
||||
return ConnectorStatusEnum.Available;
|
||||
case OCPP2_0_1.ConnectorStatusEnumType.Occupied:
|
||||
return ConnectorStatusEnum.Occupied;
|
||||
case OCPP2_0_1.ConnectorStatusEnumType.Reserved:
|
||||
return ConnectorStatusEnum.Reserved;
|
||||
case OCPP2_0_1.ConnectorStatusEnumType.Unavailable:
|
||||
return ConnectorStatusEnum.Unavailable;
|
||||
case OCPP2_0_1.ConnectorStatusEnumType.Faulted:
|
||||
return ConnectorStatusEnum.Faulted;
|
||||
default:
|
||||
return ConnectorStatusEnum.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
421
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/MeterValueMapper.ts
vendored
Normal file
421
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/MeterValueMapper.ts
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
LocationEnum,
|
||||
MeasurandEnum,
|
||||
OCPP2_0_1,
|
||||
PhaseEnum,
|
||||
ReadingContextEnum,
|
||||
type MeterValueDto,
|
||||
type SampledValue,
|
||||
} from '@citrineos/base';
|
||||
|
||||
export class MeterValueMapper {
|
||||
/**
|
||||
* Converts native ReadingContextEnum to OCPP 2.0.1 ReadingContextEnumType
|
||||
*/
|
||||
static toReadingContextEnumType(
|
||||
context?: keyof typeof ReadingContextEnum | null,
|
||||
): OCPP2_0_1.ReadingContextEnumType | undefined {
|
||||
if (!context) return undefined;
|
||||
|
||||
switch (context) {
|
||||
case 'Interruption.Begin':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Interruption_Begin;
|
||||
case 'Interruption.End':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Interruption_End;
|
||||
case 'Other':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Other;
|
||||
case 'Sample.Clock':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Sample_Clock;
|
||||
case 'Sample.Periodic':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Sample_Periodic;
|
||||
case 'Transaction.Begin':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Transaction_Begin;
|
||||
case 'Transaction.End':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Transaction_End;
|
||||
case 'Trigger':
|
||||
return OCPP2_0_1.ReadingContextEnumType.Trigger;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 2.0.1 ReadingContextEnumType to native ReadingContextEnum
|
||||
*/
|
||||
static fromReadingContextEnumType(
|
||||
context?: OCPP2_0_1.ReadingContextEnumType | null,
|
||||
): keyof typeof ReadingContextEnum | undefined {
|
||||
if (!context) return undefined;
|
||||
|
||||
switch (context) {
|
||||
case OCPP2_0_1.ReadingContextEnumType.Interruption_Begin:
|
||||
return 'Interruption.Begin';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Interruption_End:
|
||||
return 'Interruption.End';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Other:
|
||||
return 'Other';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Sample_Clock:
|
||||
return 'Sample.Clock';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Sample_Periodic:
|
||||
return 'Sample.Periodic';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Transaction_Begin:
|
||||
return 'Transaction.Begin';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Transaction_End:
|
||||
return 'Transaction.End';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Trigger:
|
||||
return 'Trigger';
|
||||
default:
|
||||
return 'Sample.Periodic';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native MeasurandEnum to OCPP 2.0.1 MeasurandEnumType
|
||||
*/
|
||||
static toMeasurandEnumType(
|
||||
measurand?: keyof typeof MeasurandEnum | null,
|
||||
): OCPP2_0_1.MeasurandEnumType | undefined {
|
||||
if (!measurand) return undefined;
|
||||
|
||||
switch (measurand) {
|
||||
case 'Current.Export':
|
||||
return OCPP2_0_1.MeasurandEnumType.Current_Export;
|
||||
case 'Current.Import':
|
||||
return OCPP2_0_1.MeasurandEnumType.Current_Import;
|
||||
case 'Current.Offered':
|
||||
return OCPP2_0_1.MeasurandEnumType.Current_Offered;
|
||||
case 'Energy.Active.Export.Register':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Active_Export_Register;
|
||||
case 'Energy.Active.Import.Register':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Active_Import_Register;
|
||||
case 'Energy.Reactive.Export.Register':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Export_Register;
|
||||
case 'Energy.Reactive.Import.Register':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Import_Register;
|
||||
case 'Energy.Active.Export.Interval':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Active_Export_Interval;
|
||||
case 'Energy.Active.Import.Interval':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Active_Import_Interval;
|
||||
case 'Energy.Active.Net':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Active_Net;
|
||||
case 'Energy.Reactive.Export.Interval':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Export_Interval;
|
||||
case 'Energy.Reactive.Import.Interval':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Import_Interval;
|
||||
case 'Energy.Reactive.Net':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Net;
|
||||
case 'Energy.Apparent.Net':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Net;
|
||||
case 'Energy.Apparent.Import':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Import;
|
||||
case 'Energy.Apparent.Export':
|
||||
return OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Export;
|
||||
case 'Frequency':
|
||||
return OCPP2_0_1.MeasurandEnumType.Frequency;
|
||||
case 'Power.Active.Export':
|
||||
return OCPP2_0_1.MeasurandEnumType.Power_Active_Export;
|
||||
case 'Power.Active.Import':
|
||||
return OCPP2_0_1.MeasurandEnumType.Power_Active_Import;
|
||||
case 'Power.Factor':
|
||||
return OCPP2_0_1.MeasurandEnumType.Power_Factor;
|
||||
case 'Power.Offered':
|
||||
return OCPP2_0_1.MeasurandEnumType.Power_Offered;
|
||||
case 'Power.Reactive.Export':
|
||||
return OCPP2_0_1.MeasurandEnumType.Power_Reactive_Export;
|
||||
case 'Power.Reactive.Import':
|
||||
return OCPP2_0_1.MeasurandEnumType.Power_Reactive_Import;
|
||||
case 'SoC':
|
||||
return OCPP2_0_1.MeasurandEnumType.SoC;
|
||||
case 'Voltage':
|
||||
return OCPP2_0_1.MeasurandEnumType.Voltage;
|
||||
default:
|
||||
// Note: Native enum measurands not supported in OCPP 2.0.1:
|
||||
// Temperature, RPM - from OCPP 1.6
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 2.0.1 MeasurandEnumType to native MeasurandEnum
|
||||
*/
|
||||
static fromMeasurandEnumType(
|
||||
measurand?: OCPP2_0_1.MeasurandEnumType | null,
|
||||
): keyof typeof MeasurandEnum | undefined {
|
||||
if (!measurand) return undefined;
|
||||
|
||||
switch (measurand) {
|
||||
case OCPP2_0_1.MeasurandEnumType.Current_Export:
|
||||
return 'Current.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Current_Import:
|
||||
return 'Current.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.Current_Offered:
|
||||
return 'Current.Offered';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Export_Register:
|
||||
return 'Energy.Active.Export.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Import_Register:
|
||||
return 'Energy.Active.Import.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Export_Register:
|
||||
return 'Energy.Reactive.Export.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Import_Register:
|
||||
return 'Energy.Reactive.Import.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Export_Interval:
|
||||
return 'Energy.Active.Export.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Import_Interval:
|
||||
return 'Energy.Active.Import.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Net:
|
||||
return 'Energy.Active.Net';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Export_Interval:
|
||||
return 'Energy.Reactive.Export.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Import_Interval:
|
||||
return 'Energy.Reactive.Import.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Net:
|
||||
return 'Energy.Reactive.Net';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Net:
|
||||
return 'Energy.Apparent.Net';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Import:
|
||||
return 'Energy.Apparent.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Export:
|
||||
return 'Energy.Apparent.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Frequency:
|
||||
return 'Frequency';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Active_Export:
|
||||
return 'Power.Active.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Active_Import:
|
||||
return 'Power.Active.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Factor:
|
||||
return 'Power.Factor';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Offered:
|
||||
return 'Power.Offered';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Reactive_Export:
|
||||
return 'Power.Reactive.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Reactive_Import:
|
||||
return 'Power.Reactive.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.SoC:
|
||||
return 'SoC';
|
||||
case OCPP2_0_1.MeasurandEnumType.Voltage:
|
||||
return 'Voltage';
|
||||
default:
|
||||
return 'Energy.Active.Import.Register';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native LocationEnum to OCPP 2.0.1 LocationEnumType
|
||||
*/
|
||||
static toLocationEnumType(
|
||||
location?: keyof typeof LocationEnum | null,
|
||||
): OCPP2_0_1.LocationEnumType | undefined {
|
||||
if (!location) return undefined;
|
||||
|
||||
switch (location) {
|
||||
case 'Body':
|
||||
return OCPP2_0_1.LocationEnumType.Body;
|
||||
case 'Cable':
|
||||
return OCPP2_0_1.LocationEnumType.Cable;
|
||||
case 'EV':
|
||||
return OCPP2_0_1.LocationEnumType.EV;
|
||||
case 'Inlet':
|
||||
return OCPP2_0_1.LocationEnumType.Inlet;
|
||||
case 'Outlet':
|
||||
return OCPP2_0_1.LocationEnumType.Outlet;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 2.0.1 LocationEnumType to native LocationEnum
|
||||
*/
|
||||
static fromLocationEnumType(
|
||||
location?: OCPP2_0_1.LocationEnumType | null,
|
||||
): keyof typeof LocationEnum | undefined {
|
||||
if (!location) return undefined;
|
||||
|
||||
switch (location) {
|
||||
case OCPP2_0_1.LocationEnumType.Body:
|
||||
return 'Body';
|
||||
case OCPP2_0_1.LocationEnumType.Cable:
|
||||
return 'Cable';
|
||||
case OCPP2_0_1.LocationEnumType.EV:
|
||||
return 'EV';
|
||||
case OCPP2_0_1.LocationEnumType.Inlet:
|
||||
return 'Inlet';
|
||||
case OCPP2_0_1.LocationEnumType.Outlet:
|
||||
return 'Outlet';
|
||||
default:
|
||||
return 'Outlet';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts native PhaseEnum to OCPP 2.0.1 PhaseEnumType
|
||||
*/
|
||||
static toPhaseEnumType(
|
||||
phase?: keyof typeof PhaseEnum | null,
|
||||
): OCPP2_0_1.PhaseEnumType | undefined {
|
||||
if (!phase) return undefined;
|
||||
|
||||
switch (phase) {
|
||||
case 'L1':
|
||||
return OCPP2_0_1.PhaseEnumType.L1;
|
||||
case 'L2':
|
||||
return OCPP2_0_1.PhaseEnumType.L2;
|
||||
case 'L3':
|
||||
return OCPP2_0_1.PhaseEnumType.L3;
|
||||
case 'N':
|
||||
return OCPP2_0_1.PhaseEnumType.N;
|
||||
case 'L1-N':
|
||||
return OCPP2_0_1.PhaseEnumType.L1_N;
|
||||
case 'L2-N':
|
||||
return OCPP2_0_1.PhaseEnumType.L2_N;
|
||||
case 'L3-N':
|
||||
return OCPP2_0_1.PhaseEnumType.L3_N;
|
||||
case 'L1-L2':
|
||||
return OCPP2_0_1.PhaseEnumType.L1_L2;
|
||||
case 'L2-L3':
|
||||
return OCPP2_0_1.PhaseEnumType.L2_L3;
|
||||
case 'L3-L1':
|
||||
return OCPP2_0_1.PhaseEnumType.L3_L1;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP 2.0.1 PhaseEnumType to native PhaseEnum
|
||||
*/
|
||||
static fromPhaseEnumType(
|
||||
phase?: OCPP2_0_1.PhaseEnumType | null,
|
||||
): keyof typeof PhaseEnum | undefined {
|
||||
if (!phase) return undefined;
|
||||
|
||||
switch (phase) {
|
||||
case OCPP2_0_1.PhaseEnumType.L1:
|
||||
return 'L1';
|
||||
case OCPP2_0_1.PhaseEnumType.L2:
|
||||
return 'L2';
|
||||
case OCPP2_0_1.PhaseEnumType.L3:
|
||||
return 'L3';
|
||||
case OCPP2_0_1.PhaseEnumType.N:
|
||||
return 'N';
|
||||
case OCPP2_0_1.PhaseEnumType.L1_N:
|
||||
return 'L1-N';
|
||||
case OCPP2_0_1.PhaseEnumType.L2_N:
|
||||
return 'L2-N';
|
||||
case OCPP2_0_1.PhaseEnumType.L3_N:
|
||||
return 'L3-N';
|
||||
case OCPP2_0_1.PhaseEnumType.L1_L2:
|
||||
return 'L1-L2';
|
||||
case OCPP2_0_1.PhaseEnumType.L2_L3:
|
||||
return 'L2-L3';
|
||||
case OCPP2_0_1.PhaseEnumType.L3_L1:
|
||||
return 'L3-L1';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static toMeterValueType(meterValue: MeterValueDto): OCPP2_0_1.MeterValueType {
|
||||
return {
|
||||
timestamp: meterValue.timestamp,
|
||||
sampledValue: MeterValueMapper.toSampledValueTypes(meterValue.sampledValue),
|
||||
};
|
||||
}
|
||||
|
||||
static toSampledValueTypes(
|
||||
sampledValues: SampledValue[],
|
||||
): [OCPP2_0_1.SampledValueType, ...OCPP2_0_1.SampledValueType[]] {
|
||||
if (!(sampledValues instanceof Array) || sampledValues.length === 0) {
|
||||
throw new Error(`Invalid sampledValues: ${JSON.stringify(sampledValues)}`);
|
||||
}
|
||||
|
||||
const sampledValueTypes: OCPP2_0_1.SampledValueType[] = [];
|
||||
for (const sampledValue of sampledValues) {
|
||||
const measurand = MeterValueMapper.toMeasurandEnumType(sampledValue.measurand);
|
||||
if (measurand !== undefined) {
|
||||
sampledValueTypes.push({
|
||||
value: sampledValue.value,
|
||||
context: MeterValueMapper.toReadingContextEnumType(sampledValue.context),
|
||||
measurand: measurand,
|
||||
phase: MeterValueMapper.toPhaseEnumType(sampledValue.phase),
|
||||
location: MeterValueMapper.toLocationEnumType(sampledValue.location),
|
||||
signedMeterValue: sampledValue.signedMeterValue
|
||||
? {
|
||||
signedMeterData: sampledValue.signedMeterValue.signedMeterData,
|
||||
signingMethod: sampledValue.signedMeterValue.signingMethod,
|
||||
encodingMethod: sampledValue.signedMeterValue.encodingMethod,
|
||||
publicKey: sampledValue.signedMeterValue.publicKey,
|
||||
}
|
||||
: undefined,
|
||||
unitOfMeasure: sampledValue.unitOfMeasure
|
||||
? {
|
||||
unit: sampledValue.unitOfMeasure.unit,
|
||||
multiplier: sampledValue.unitOfMeasure.multiplier,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
} else {
|
||||
console.warn(`Unsupported measurand for OCPP 2.0.1: ${sampledValue.measurand}`);
|
||||
}
|
||||
}
|
||||
return sampledValueTypes as [OCPP2_0_1.SampledValueType, ...OCPP2_0_1.SampledValueType[]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP2_0_1.SampledValueType[] back to SampledValue[]
|
||||
*/
|
||||
static fromSampledValueTypes(
|
||||
sampledValueTypes: OCPP2_0_1.SampledValueType[],
|
||||
): [SampledValue, ...SampledValue[]] {
|
||||
if (!Array.isArray(sampledValueTypes) || sampledValueTypes.length === 0) {
|
||||
throw new Error(`Invalid sampledValueTypes: ${JSON.stringify(sampledValueTypes)}`);
|
||||
}
|
||||
|
||||
const sampledValues: SampledValue[] = [];
|
||||
for (const sampledValueType of sampledValueTypes) {
|
||||
const sampledValue: SampledValue = {
|
||||
value: sampledValueType.value,
|
||||
context: MeterValueMapper.fromReadingContextEnumType(sampledValueType.context),
|
||||
measurand: MeterValueMapper.fromMeasurandEnumType(sampledValueType.measurand),
|
||||
phase: MeterValueMapper.fromPhaseEnumType(sampledValueType.phase),
|
||||
location: MeterValueMapper.fromLocationEnumType(sampledValueType.location),
|
||||
};
|
||||
|
||||
if (sampledValueType.signedMeterValue) {
|
||||
sampledValue.signedMeterValue = {
|
||||
signedMeterData: sampledValueType.signedMeterValue.signedMeterData,
|
||||
signingMethod: sampledValueType.signedMeterValue.signingMethod,
|
||||
encodingMethod: sampledValueType.signedMeterValue.encodingMethod,
|
||||
publicKey: sampledValueType.signedMeterValue.publicKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (sampledValueType.unitOfMeasure) {
|
||||
sampledValue.unitOfMeasure = {
|
||||
unit:
|
||||
sampledValueType.unitOfMeasure.unit ||
|
||||
(sampledValue.measurand?.startsWith('Energy') ? 'Wh' : undefined),
|
||||
multiplier: sampledValueType.unitOfMeasure.multiplier || 0,
|
||||
};
|
||||
}
|
||||
|
||||
sampledValues.push(sampledValue);
|
||||
}
|
||||
|
||||
return sampledValues as [SampledValue, ...SampledValue[]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts OCPP2_0_1.MeterValueType back to a partial MeterValue structure
|
||||
*/
|
||||
static fromMeterValueType(meterValueType: OCPP2_0_1.MeterValueType): MeterValueDto {
|
||||
return {
|
||||
timestamp: meterValueType.timestamp,
|
||||
sampledValue: MeterValueMapper.fromSampledValueTypes(meterValueType.sampledValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { OCPP2_0_1 } from '@citrineos/base';
|
||||
import { Transaction } from '../../model/TransactionEvent/Transaction.js';
|
||||
|
||||
export class TransactionMapper {
|
||||
static toTransactionType(transaction: Transaction): OCPP2_0_1.TransactionType {
|
||||
return {
|
||||
transactionId: transaction.transactionId,
|
||||
chargingState: transaction.chargingState as OCPP2_0_1.ChargingStateEnumType,
|
||||
timeSpentCharging: transaction.timeSpentCharging,
|
||||
stoppedReason: transaction.stoppedReason as OCPP2_0_1.ReasonEnumType,
|
||||
remoteStartId: transaction.remoteStartId,
|
||||
customData: transaction.customData,
|
||||
};
|
||||
}
|
||||
}
|
||||
10
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/index.ts
vendored
Normal file
10
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.0.1/index.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { AuthorizationMapper } from './AuthorizationMapper.js';
|
||||
export { BootMapper } from './BootMapper.js';
|
||||
export { ChargingProfileMapper } from './ChargingProfileMapper.js';
|
||||
export { MeterValueMapper } from './MeterValueMapper.js';
|
||||
export { TransactionMapper } from './TransactionMapper.js';
|
||||
export { LocationMapper } from './LocationMapper.js';
|
||||
182
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.1/AuthorizationMapper.ts
vendored
Normal file
182
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.1/AuthorizationMapper.ts
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { AuthorizationStatusEnumType, IdTokenEnumType } from '@citrineos/base';
|
||||
import { AuthorizationStatusEnum, IdTokenEnum, OCPP2_1 } from '@citrineos/base';
|
||||
import { Authorization } from '../../model/Authorization/Authorization.js';
|
||||
|
||||
export class AuthorizationMapper {
|
||||
static toAuthorizationData(authorization: Authorization): OCPP2_1.AuthorizationData {
|
||||
return {
|
||||
customData: authorization.customData,
|
||||
idToken: AuthorizationMapper.toIdToken(authorization),
|
||||
idTokenInfo: AuthorizationMapper.toIdTokenInfo(authorization),
|
||||
};
|
||||
}
|
||||
|
||||
static toIdToken(authorization: Authorization): OCPP2_1.IdTokenType {
|
||||
if (!authorization.idTokenType) {
|
||||
throw new Error('IdToken type is missing.');
|
||||
}
|
||||
return {
|
||||
customData: authorization.customData,
|
||||
additionalInfo: authorization.additionalInfo ?? null,
|
||||
idToken: authorization.idToken,
|
||||
type: AuthorizationMapper.toIdTokenEnumType(authorization.idTokenType),
|
||||
};
|
||||
}
|
||||
|
||||
static toIdTokenInfo(authorization: Authorization): OCPP2_1.IdTokenInfoType {
|
||||
return {
|
||||
status: AuthorizationMapper.fromAuthorizationStatusEnumType(authorization.status),
|
||||
cacheExpiryDateTime: authorization.cacheExpiryDateTime,
|
||||
chargingPriority: authorization.chargingPriority,
|
||||
language1: authorization.language1,
|
||||
language2: authorization.language2,
|
||||
personalMessage: authorization.personalMessage,
|
||||
customData: authorization.customData,
|
||||
};
|
||||
}
|
||||
|
||||
static toMessageContentType(messageContent: any): OCPP2_1.MessageContentType {
|
||||
return {
|
||||
customData: messageContent.customData,
|
||||
format: AuthorizationMapper.toMessageFormatEnum(messageContent.format),
|
||||
language: messageContent.language,
|
||||
content: messageContent.content,
|
||||
};
|
||||
}
|
||||
|
||||
static toMessageFormatEnum(messageFormat: string): OCPP2_1.MessageFormatEnumType {
|
||||
switch (messageFormat) {
|
||||
case 'ASCII':
|
||||
return OCPP2_1.MessageFormatEnumType.ASCII;
|
||||
case 'HTML':
|
||||
return OCPP2_1.MessageFormatEnumType.HTML;
|
||||
case 'URI':
|
||||
return OCPP2_1.MessageFormatEnumType.URI;
|
||||
case 'UTF8':
|
||||
return OCPP2_1.MessageFormatEnumType.UTF8;
|
||||
case 'QRCODE':
|
||||
return OCPP2_1.MessageFormatEnumType.QRCODE;
|
||||
default:
|
||||
throw new Error('Unknown message format');
|
||||
}
|
||||
}
|
||||
|
||||
static fromAuthorizationStatusEnumType(
|
||||
status: AuthorizationStatusEnumType,
|
||||
): OCPP2_1.AuthorizationStatusEnumType {
|
||||
switch (status) {
|
||||
case AuthorizationStatusEnum.Accepted:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.Accepted;
|
||||
case AuthorizationStatusEnum.Blocked:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.Blocked;
|
||||
case AuthorizationStatusEnum.ConcurrentTx:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.ConcurrentTx;
|
||||
case AuthorizationStatusEnum.Expired:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.Expired;
|
||||
case AuthorizationStatusEnum.Invalid:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.Invalid;
|
||||
case AuthorizationStatusEnum.NoCredit:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.NoCredit;
|
||||
case AuthorizationStatusEnum.NotAllowedTypeEVSE:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.NotAllowedTypeEVSE;
|
||||
case AuthorizationStatusEnum.NotAtThisLocation:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.NotAtThisLocation;
|
||||
case AuthorizationStatusEnum.NotAtThisTime:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.NotAtThisTime;
|
||||
case AuthorizationStatusEnum.Unknown:
|
||||
return OCPP2_1.AuthorizationStatusEnumType.Unknown;
|
||||
default:
|
||||
throw new Error('Unknown authorization status: ' + status);
|
||||
}
|
||||
}
|
||||
|
||||
static toAuthorizationStatusEnumType(
|
||||
status: OCPP2_1.AuthorizationStatusEnumType,
|
||||
): AuthorizationStatusEnumType {
|
||||
switch (status) {
|
||||
case OCPP2_1.AuthorizationStatusEnumType.Accepted:
|
||||
return AuthorizationStatusEnum.Accepted;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.Blocked:
|
||||
return AuthorizationStatusEnum.Blocked;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.ConcurrentTx:
|
||||
return AuthorizationStatusEnum.ConcurrentTx;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.Expired:
|
||||
return AuthorizationStatusEnum.Expired;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.Invalid:
|
||||
return AuthorizationStatusEnum.Invalid;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.NoCredit:
|
||||
return AuthorizationStatusEnum.NoCredit;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.NotAllowedTypeEVSE:
|
||||
return AuthorizationStatusEnum.NotAllowedTypeEVSE;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.NotAtThisLocation:
|
||||
return AuthorizationStatusEnum.NotAtThisLocation;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.NotAtThisTime:
|
||||
return AuthorizationStatusEnum.NotAtThisTime;
|
||||
case OCPP2_1.AuthorizationStatusEnumType.Unknown:
|
||||
return AuthorizationStatusEnum.Unknown;
|
||||
default:
|
||||
throw new Error('Unknown authorization status');
|
||||
}
|
||||
}
|
||||
|
||||
static toIdTokenEnumType(type: IdTokenEnumType): OCPP2_1.IdTokenEnumType {
|
||||
switch (type) {
|
||||
case IdTokenEnum.Central:
|
||||
return OCPP2_1.IdTokenEnumType.Central;
|
||||
case IdTokenEnum.DirectPayment:
|
||||
return OCPP2_1.IdTokenEnumType.DirectPayment;
|
||||
case IdTokenEnum.eMAID:
|
||||
return OCPP2_1.IdTokenEnumType.eMAID;
|
||||
case IdTokenEnum.EVCCID:
|
||||
return OCPP2_1.IdTokenEnumType.EVCCID;
|
||||
case IdTokenEnum.ISO14443:
|
||||
return OCPP2_1.IdTokenEnumType.ISO14443;
|
||||
case IdTokenEnum.ISO15693:
|
||||
return OCPP2_1.IdTokenEnumType.ISO15693;
|
||||
case IdTokenEnum.KeyCode:
|
||||
return OCPP2_1.IdTokenEnumType.KeyCode;
|
||||
case IdTokenEnum.Local:
|
||||
return OCPP2_1.IdTokenEnumType.Local;
|
||||
case IdTokenEnum.MacAddress:
|
||||
return OCPP2_1.IdTokenEnumType.MacAddress;
|
||||
case IdTokenEnum.NoAuthorization:
|
||||
return OCPP2_1.IdTokenEnumType.NoAuthorization;
|
||||
case IdTokenEnum.VIN:
|
||||
return OCPP2_1.IdTokenEnumType.VIN;
|
||||
default:
|
||||
throw new Error(`Unknown idToken type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
static fromIdTokenEnumType(type: OCPP2_1.IdTokenEnumType): IdTokenEnumType {
|
||||
switch (type) {
|
||||
case OCPP2_1.IdTokenEnumType.Central:
|
||||
return IdTokenEnum.Central;
|
||||
case OCPP2_1.IdTokenEnumType.DirectPayment:
|
||||
return IdTokenEnum.DirectPayment;
|
||||
case OCPP2_1.IdTokenEnumType.eMAID:
|
||||
return IdTokenEnum.eMAID;
|
||||
case OCPP2_1.IdTokenEnumType.EVCCID:
|
||||
return IdTokenEnum.EVCCID;
|
||||
case OCPP2_1.IdTokenEnumType.ISO14443:
|
||||
return IdTokenEnum.ISO14443;
|
||||
case OCPP2_1.IdTokenEnumType.ISO15693:
|
||||
return IdTokenEnum.ISO15693;
|
||||
case OCPP2_1.IdTokenEnumType.KeyCode:
|
||||
return IdTokenEnum.KeyCode;
|
||||
case OCPP2_1.IdTokenEnumType.Local:
|
||||
return IdTokenEnum.Local;
|
||||
case OCPP2_1.IdTokenEnumType.MacAddress:
|
||||
return IdTokenEnum.MacAddress;
|
||||
case OCPP2_1.IdTokenEnumType.NoAuthorization:
|
||||
return IdTokenEnum.NoAuthorization;
|
||||
case OCPP2_1.IdTokenEnumType.VIN:
|
||||
return IdTokenEnum.VIN;
|
||||
default:
|
||||
throw new Error(`Unknown OCPP 2.1 idToken type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.1/TariffMapper.ts
vendored
Normal file
69
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.1/TariffMapper.ts
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
TariffEnergyType,
|
||||
TariffFixedType,
|
||||
TariffTimeType,
|
||||
PriceType,
|
||||
MessageContentType,
|
||||
} from '@citrineos/base';
|
||||
import { OCPP2_1 } from '@citrineos/base';
|
||||
import { Tariff } from '../../model/Tariff/Tariffs.js';
|
||||
export class TariffMapper {
|
||||
/**
|
||||
* Maps a {@link Tariff} DB model to an OCPP 2.1 {@link OCPP2_1.TariffType}.
|
||||
*
|
||||
* - `tariffId` falls back to the DB primary key string when not explicitly set.
|
||||
* - All complex fields (`energy`, `chargingTime`, etc.) are stored as JSONB and
|
||||
* passed through directly; their structure is validated at the OCPP message boundary.
|
||||
*
|
||||
* @throws {Error} if `currency` is missing (required by spec).
|
||||
*/
|
||||
static toTariffType(tariff: Tariff): OCPP2_1.TariffType {
|
||||
if (!tariff.currency) {
|
||||
throw new Error(`Tariff id=${tariff.id} is missing required field: currency`);
|
||||
}
|
||||
|
||||
return {
|
||||
tariffId: tariff.tariffId ?? String(tariff.id),
|
||||
currency: tariff.currency,
|
||||
validFrom: tariff.validFrom ?? undefined,
|
||||
description: TariffMapper.toDescription(tariff.description),
|
||||
energy: TariffMapper.toEnergyType(tariff.energy),
|
||||
chargingTime: TariffMapper.toTimeType(tariff.chargingTime),
|
||||
idleTime: TariffMapper.toTimeType(tariff.idleTime),
|
||||
fixedFee: TariffMapper.toFixedType(tariff.fixedFee),
|
||||
reservationTime: TariffMapper.toTimeType(tariff.reservationTime),
|
||||
reservationFixed: TariffMapper.toFixedType(tariff.reservationFixed),
|
||||
minCost: TariffMapper.toPriceType(tariff.minCost),
|
||||
maxCost: TariffMapper.toPriceType(tariff.maxCost),
|
||||
};
|
||||
}
|
||||
|
||||
static toDescription(
|
||||
description: MessageContentType[] | null | undefined,
|
||||
): OCPP2_1.TariffType['description'] | undefined {
|
||||
return (description as OCPP2_1.TariffType['description']) ?? undefined;
|
||||
}
|
||||
|
||||
static toEnergyType(
|
||||
energy: TariffEnergyType | null | undefined,
|
||||
): OCPP2_1.TariffEnergyType | undefined {
|
||||
return (energy as OCPP2_1.TariffEnergyType) ?? undefined;
|
||||
}
|
||||
|
||||
static toTimeType(time: TariffTimeType | null | undefined): OCPP2_1.TariffTimeType | undefined {
|
||||
return (time as OCPP2_1.TariffTimeType) ?? undefined;
|
||||
}
|
||||
|
||||
static toFixedType(
|
||||
fixed: TariffFixedType | null | undefined,
|
||||
): OCPP2_1.TariffFixedType | undefined {
|
||||
return (fixed as OCPP2_1.TariffFixedType) ?? undefined;
|
||||
}
|
||||
|
||||
static toPriceType(price: PriceType | null | undefined): OCPP2_1.PriceType | undefined {
|
||||
return (price as OCPP2_1.PriceType) ?? undefined;
|
||||
}
|
||||
}
|
||||
6
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.1/index.ts
vendored
Normal file
6
tools/citrineos-core-main/packages/core/src/dal/layers/sequelize/mapper/2.1/index.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { AuthorizationMapper } from './AuthorizationMapper.js';
|
||||
export { TariffMapper } from './TariffMapper.js';
|
||||
@@ -0,0 +1,258 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
LocationEnum,
|
||||
MeasurandEnum,
|
||||
OCPP2_0_1,
|
||||
OCPP2_1,
|
||||
OCPP2_common_types,
|
||||
PhaseEnum,
|
||||
ReadingContextEnum,
|
||||
type MeterValueDto,
|
||||
type SampledValue,
|
||||
} from '@citrineos/base';
|
||||
|
||||
export class MeterValueMapper {
|
||||
static fromMeterValueType(meterValueType: OCPP2_common_types.MeterValueType): MeterValueDto {
|
||||
return {
|
||||
timestamp: meterValueType.timestamp,
|
||||
sampledValue: MeterValueMapper.fromSampledValueTypes(
|
||||
meterValueType.sampledValue as [
|
||||
OCPP2_common_types.SampledValueType,
|
||||
...OCPP2_common_types.SampledValueType[],
|
||||
],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
static fromSampledValueTypes(
|
||||
sampledValueTypes: [
|
||||
OCPP2_common_types.SampledValueType,
|
||||
...OCPP2_common_types.SampledValueType[],
|
||||
],
|
||||
): [SampledValue, ...SampledValue[]] {
|
||||
if (!Array.isArray(sampledValueTypes) || sampledValueTypes.length === 0) {
|
||||
throw new Error(`Invalid sampledValueTypes: ${JSON.stringify(sampledValueTypes)}`);
|
||||
}
|
||||
|
||||
const sampledValues: SampledValue[] = [];
|
||||
for (const sv of sampledValueTypes) {
|
||||
const sampledValue: SampledValue = {
|
||||
value: sv.value,
|
||||
context: MeterValueMapper.fromReadingContextEnumType(sv.context),
|
||||
measurand: MeterValueMapper.fromMeasurandEnumType(sv.measurand),
|
||||
phase: MeterValueMapper.fromPhaseEnumType(sv.phase),
|
||||
location: MeterValueMapper.fromLocationEnumType(sv.location),
|
||||
};
|
||||
|
||||
if (sv.signedMeterValue) {
|
||||
sampledValue.signedMeterValue = {
|
||||
signedMeterData: sv.signedMeterValue.signedMeterData,
|
||||
// 2.1 makes signingMethod/publicKey optional; fall back to empty string to satisfy internal schema
|
||||
signingMethod: sv.signedMeterValue.signingMethod ?? '',
|
||||
encodingMethod: sv.signedMeterValue.encodingMethod,
|
||||
publicKey: sv.signedMeterValue.publicKey ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
if (sv.unitOfMeasure) {
|
||||
sampledValue.unitOfMeasure = {
|
||||
unit:
|
||||
sv.unitOfMeasure.unit ||
|
||||
(sampledValue.measurand?.startsWith('Energy') ? 'Wh' : undefined),
|
||||
multiplier: sv.unitOfMeasure.multiplier || 0,
|
||||
};
|
||||
}
|
||||
|
||||
sampledValues.push(sampledValue);
|
||||
}
|
||||
|
||||
return sampledValues as [SampledValue, ...SampledValue[]];
|
||||
}
|
||||
|
||||
static fromReadingContextEnumType(
|
||||
context?: OCPP2_0_1.ReadingContextEnumType | OCPP2_1.ReadingContextEnumType | null,
|
||||
): keyof typeof ReadingContextEnum | undefined {
|
||||
if (!context) return undefined;
|
||||
switch (context) {
|
||||
case OCPP2_0_1.ReadingContextEnumType.Interruption_Begin:
|
||||
case OCPP2_1.ReadingContextEnumType.Interruption_Begin:
|
||||
return 'Interruption.Begin';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Interruption_End:
|
||||
case OCPP2_1.ReadingContextEnumType.Interruption_End:
|
||||
return 'Interruption.End';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Other:
|
||||
case OCPP2_1.ReadingContextEnumType.Other:
|
||||
return 'Other';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Sample_Clock:
|
||||
case OCPP2_1.ReadingContextEnumType.Sample_Clock:
|
||||
return 'Sample.Clock';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Sample_Periodic:
|
||||
case OCPP2_1.ReadingContextEnumType.Sample_Periodic:
|
||||
return 'Sample.Periodic';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Transaction_Begin:
|
||||
case OCPP2_1.ReadingContextEnumType.Transaction_Begin:
|
||||
return 'Transaction.Begin';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Transaction_End:
|
||||
case OCPP2_1.ReadingContextEnumType.Transaction_End:
|
||||
return 'Transaction.End';
|
||||
case OCPP2_0_1.ReadingContextEnumType.Trigger:
|
||||
case OCPP2_1.ReadingContextEnumType.Trigger:
|
||||
return 'Trigger';
|
||||
default:
|
||||
return 'Sample.Periodic';
|
||||
}
|
||||
}
|
||||
|
||||
static fromMeasurandEnumType(
|
||||
measurand?: OCPP2_0_1.MeasurandEnumType | OCPP2_1.MeasurandEnumType | null,
|
||||
): keyof typeof MeasurandEnum | undefined {
|
||||
if (!measurand) return undefined;
|
||||
switch (measurand) {
|
||||
case OCPP2_0_1.MeasurandEnumType.Current_Export:
|
||||
case OCPP2_1.MeasurandEnumType.Current_Export:
|
||||
return 'Current.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Current_Import:
|
||||
case OCPP2_1.MeasurandEnumType.Current_Import:
|
||||
return 'Current.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.Current_Offered:
|
||||
case OCPP2_1.MeasurandEnumType.Current_Offered:
|
||||
return 'Current.Offered';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Export_Register:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Active_Export_Register:
|
||||
return 'Energy.Active.Export.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Import_Register:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Active_Import_Register:
|
||||
return 'Energy.Active.Import.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Export_Register:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Reactive_Export_Register:
|
||||
return 'Energy.Reactive.Export.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Import_Register:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Reactive_Import_Register:
|
||||
return 'Energy.Reactive.Import.Register';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Export_Interval:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Active_Export_Interval:
|
||||
return 'Energy.Active.Export.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Import_Interval:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Active_Import_Interval:
|
||||
return 'Energy.Active.Import.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Active_Net:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Active_Net:
|
||||
return 'Energy.Active.Net';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Export_Interval:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Reactive_Export_Interval:
|
||||
return 'Energy.Reactive.Export.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Import_Interval:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Reactive_Import_Interval:
|
||||
return 'Energy.Reactive.Import.Interval';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Reactive_Net:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Reactive_Net:
|
||||
return 'Energy.Reactive.Net';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Net:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Apparent_Net:
|
||||
return 'Energy.Apparent.Net';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Import:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Apparent_Import:
|
||||
return 'Energy.Apparent.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.Energy_Apparent_Export:
|
||||
case OCPP2_1.MeasurandEnumType.Energy_Apparent_Export:
|
||||
return 'Energy.Apparent.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Frequency:
|
||||
case OCPP2_1.MeasurandEnumType.Frequency:
|
||||
return 'Frequency';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Active_Export:
|
||||
case OCPP2_1.MeasurandEnumType.Power_Active_Export:
|
||||
return 'Power.Active.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Active_Import:
|
||||
case OCPP2_1.MeasurandEnumType.Power_Active_Import:
|
||||
return 'Power.Active.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Factor:
|
||||
case OCPP2_1.MeasurandEnumType.Power_Factor:
|
||||
return 'Power.Factor';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Offered:
|
||||
case OCPP2_1.MeasurandEnumType.Power_Offered:
|
||||
return 'Power.Offered';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Reactive_Export:
|
||||
case OCPP2_1.MeasurandEnumType.Power_Reactive_Export:
|
||||
return 'Power.Reactive.Export';
|
||||
case OCPP2_0_1.MeasurandEnumType.Power_Reactive_Import:
|
||||
case OCPP2_1.MeasurandEnumType.Power_Reactive_Import:
|
||||
return 'Power.Reactive.Import';
|
||||
case OCPP2_0_1.MeasurandEnumType.SoC:
|
||||
case OCPP2_1.MeasurandEnumType.SoC:
|
||||
return 'SoC';
|
||||
case OCPP2_0_1.MeasurandEnumType.Voltage:
|
||||
case OCPP2_1.MeasurandEnumType.Voltage:
|
||||
return 'Voltage';
|
||||
default:
|
||||
return 'Energy.Active.Import.Register';
|
||||
}
|
||||
}
|
||||
|
||||
static fromPhaseEnumType(
|
||||
phase?: OCPP2_0_1.PhaseEnumType | OCPP2_1.PhaseEnumType | null,
|
||||
): keyof typeof PhaseEnum | undefined {
|
||||
if (!phase) return undefined;
|
||||
switch (phase) {
|
||||
case OCPP2_0_1.PhaseEnumType.L1:
|
||||
case OCPP2_1.PhaseEnumType.L1:
|
||||
return 'L1';
|
||||
case OCPP2_0_1.PhaseEnumType.L2:
|
||||
case OCPP2_1.PhaseEnumType.L2:
|
||||
return 'L2';
|
||||
case OCPP2_0_1.PhaseEnumType.L3:
|
||||
case OCPP2_1.PhaseEnumType.L3:
|
||||
return 'L3';
|
||||
case OCPP2_0_1.PhaseEnumType.N:
|
||||
case OCPP2_1.PhaseEnumType.N:
|
||||
return 'N';
|
||||
case OCPP2_0_1.PhaseEnumType.L1_N:
|
||||
case OCPP2_1.PhaseEnumType.L1_N:
|
||||
return 'L1-N';
|
||||
case OCPP2_0_1.PhaseEnumType.L2_N:
|
||||
case OCPP2_1.PhaseEnumType.L2_N:
|
||||
return 'L2-N';
|
||||
case OCPP2_0_1.PhaseEnumType.L3_N:
|
||||
case OCPP2_1.PhaseEnumType.L3_N:
|
||||
return 'L3-N';
|
||||
case OCPP2_0_1.PhaseEnumType.L1_L2:
|
||||
case OCPP2_1.PhaseEnumType.L1_L2:
|
||||
return 'L1-L2';
|
||||
case OCPP2_0_1.PhaseEnumType.L2_L3:
|
||||
case OCPP2_1.PhaseEnumType.L2_L3:
|
||||
return 'L2-L3';
|
||||
case OCPP2_0_1.PhaseEnumType.L3_L1:
|
||||
case OCPP2_1.PhaseEnumType.L3_L1:
|
||||
return 'L3-L1';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static fromLocationEnumType(
|
||||
location?: OCPP2_0_1.LocationEnumType | OCPP2_1.LocationEnumType | null,
|
||||
): keyof typeof LocationEnum | undefined {
|
||||
if (!location) return undefined;
|
||||
switch (location) {
|
||||
case OCPP2_0_1.LocationEnumType.Body:
|
||||
case OCPP2_1.LocationEnumType.Body:
|
||||
return 'Body';
|
||||
case OCPP2_0_1.LocationEnumType.Cable:
|
||||
case OCPP2_1.LocationEnumType.Cable:
|
||||
return 'Cable';
|
||||
case OCPP2_0_1.LocationEnumType.EV:
|
||||
case OCPP2_1.LocationEnumType.EV:
|
||||
return 'EV';
|
||||
case OCPP2_0_1.LocationEnumType.Inlet:
|
||||
case OCPP2_1.LocationEnumType.Inlet:
|
||||
return 'Inlet';
|
||||
case OCPP2_0_1.LocationEnumType.Outlet:
|
||||
case OCPP2_1.LocationEnumType.Outlet:
|
||||
return 'Outlet';
|
||||
default:
|
||||
return 'Outlet';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TenantDto, TenantPartnerDto } from '@citrineos/base';
|
||||
import { type AsyncJobNameEnumType, DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
Default,
|
||||
ForeignKey,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { TenantPartner } from '../TenantPartner.js';
|
||||
|
||||
export interface PaginatedParams {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
dateFrom?: Date;
|
||||
dateTo?: Date;
|
||||
}
|
||||
|
||||
@Table
|
||||
export class AsyncJobStatus extends Model {
|
||||
static readonly MODEL_NAME: string = 'AsyncJobStatus';
|
||||
|
||||
@PrimaryKey
|
||||
@Default(() => uuidv4()) // Automatically generate jobId
|
||||
@Column(DataType.STRING)
|
||||
declare jobId: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare jobName: AsyncJobNameEnumType;
|
||||
|
||||
@ForeignKey(() => TenantPartner)
|
||||
@Column(DataType.INTEGER)
|
||||
declare tenantPartnerId: number;
|
||||
|
||||
@BelongsTo(() => TenantPartner, { foreignKey: 'tenantPartnerId', as: 'asyncJobTenantPartner' })
|
||||
declare tenantPartner: TenantPartnerDto;
|
||||
|
||||
@Column(DataType.DATE)
|
||||
declare finishedAt?: Date;
|
||||
|
||||
@Column(DataType.DATE)
|
||||
declare stoppedAt?: Date | null;
|
||||
|
||||
@Default(false)
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare stopScheduled: boolean;
|
||||
|
||||
@Default(false)
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare isFailed: boolean;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare paginationParams: PaginatedParams;
|
||||
|
||||
@Column(DataType.INTEGER) // Total number of objects in the client's system
|
||||
declare totalObjects?: number;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: AsyncJobStatus) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
toDTO(): AsyncJobStatusDTO {
|
||||
return {
|
||||
jobId: this.jobId,
|
||||
jobName: this.jobName,
|
||||
tenantPartnerId: this.tenantPartnerId,
|
||||
tenantPartner: this.tenantPartner,
|
||||
createdAt: this.createdAt,
|
||||
finishedAt: this.finishedAt,
|
||||
stoppedAt: this.stoppedAt,
|
||||
stopScheduled: this.stopScheduled,
|
||||
isFailed: this.isFailed,
|
||||
paginatedParams: this.paginationParams,
|
||||
totalObjects: this.totalObjects,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class AsyncJobStatusDTO {
|
||||
jobId!: string;
|
||||
jobName!: AsyncJobNameEnumType;
|
||||
tenantPartnerId!: number;
|
||||
tenantPartner?: TenantPartnerDto;
|
||||
createdAt!: Date;
|
||||
finishedAt?: Date;
|
||||
stoppedAt?: Date | null;
|
||||
stopScheduled!: boolean;
|
||||
isFailed?: boolean;
|
||||
paginatedParams!: PaginatedParams;
|
||||
totalObjects?: number;
|
||||
}
|
||||
|
||||
export class AsyncJobRequest {
|
||||
tenantPartnerId!: number;
|
||||
paginatedParams!: PaginatedParams;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './AsyncJobStatus.js';
|
||||
@@ -0,0 +1,164 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type {
|
||||
AdditionalInfo,
|
||||
AuthorizationDto,
|
||||
AuthorizationStatusEnumType,
|
||||
AuthorizationWhitelistEnumType,
|
||||
IdTokenEnumType,
|
||||
RealTimeAuthLastAttempt,
|
||||
TariffDto,
|
||||
TenantDto,
|
||||
TenantPartnerDto,
|
||||
TransactionDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
Default,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { TenantPartner } from '../TenantPartner.js';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
import { Tariff } from '../Tariff/Tariffs.js';
|
||||
|
||||
@Table
|
||||
export class Authorization extends Model implements AuthorizationDto {
|
||||
static readonly MODEL_NAME: string = Namespace.AuthorizationData;
|
||||
|
||||
@Column(DataType.ARRAY(DataType.STRING))
|
||||
declare allowedConnectorTypes?: string[];
|
||||
|
||||
@Column(DataType.ARRAY(DataType.STRING))
|
||||
declare disallowedEvseIdPrefixes?: string[];
|
||||
|
||||
@Column({
|
||||
type: DataType.CITEXT,
|
||||
unique: 'idToken_type',
|
||||
})
|
||||
declare idToken: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'idToken_type',
|
||||
})
|
||||
declare idTokenType?: IdTokenEnumType | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare additionalInfo?: [AdditionalInfo, ...AdditionalInfo[]] | null; // JSONB for AdditionalInfo
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare status: AuthorizationStatusEnumType;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
return this.getDataValue('cacheExpiryDateTime')?.toISOString();
|
||||
},
|
||||
})
|
||||
declare cacheExpiryDateTime?: string | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare chargingPriority?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare language1?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare language2?: string | null;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare personalMessage?: any | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare realTimeAuth?: AuthorizationWhitelistEnumType | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare realTimeAuthLastAttempt?: RealTimeAuthLastAttempt | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare realTimeAuthTimeout?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare realTimeAuthUrl?: string;
|
||||
|
||||
// Reference to another Authorization for groupAuthorization
|
||||
@ForeignKey(() => Authorization)
|
||||
@Column(DataType.INTEGER)
|
||||
declare groupAuthorizationId?: number | null;
|
||||
|
||||
@ForeignKey(() => Tariff)
|
||||
@Column(DataType.INTEGER)
|
||||
declare tariffId?: number | null;
|
||||
|
||||
@BelongsTo(() => Authorization, { foreignKey: 'groupAuthorizationId', as: 'groupAuthorization' })
|
||||
declare groupAuthorization?: Authorization;
|
||||
|
||||
@BelongsTo(() => Tariff, { foreignKey: 'tariffId', as: 'tariff' })
|
||||
declare tariff?: TariffDto | null;
|
||||
|
||||
@Default(false)
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare concurrentTransaction?: boolean;
|
||||
|
||||
@Default(false)
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare isPrepaid?: boolean;
|
||||
|
||||
@Column(DataType.DECIMAL)
|
||||
declare prepaidBalance?: number | null;
|
||||
|
||||
declare customData?: any | null;
|
||||
|
||||
// For cases where Authorization is owned by an upstream partner, i.e. an eMSP
|
||||
@ForeignKey(() => TenantPartner)
|
||||
@Column(DataType.INTEGER)
|
||||
declare tenantPartnerId?: number | null;
|
||||
|
||||
@BelongsTo(() => TenantPartner, {
|
||||
foreignKey: 'tenantPartnerId',
|
||||
as: 'authTenantPartnerAuthorization',
|
||||
})
|
||||
declare tenantPartner?: TenantPartnerDto | null;
|
||||
|
||||
@HasMany(() => Transaction, 'authorizationId')
|
||||
declare transactions?: TransactionDto[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
unique: 'idToken_type',
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Authorization) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import type {
|
||||
TenantDto,
|
||||
AuthorizationDto,
|
||||
LocalListVersionDto,
|
||||
SendLocalListDto,
|
||||
} from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { type AuthorizationRestrictions } from '@dal/interfaces/projections/AuthorizationRestrictions.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Authorization } from './Authorization.js';
|
||||
import { SendLocalList } from './SendLocalList.js';
|
||||
import { SendLocalListAuthorization } from './SendLocalListAuthorization.js';
|
||||
import { LocalListVersion } from './LocalListVersion.js';
|
||||
import { LocalListVersionAuthorization } from './LocalListVersionAuthorization.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* This class represents static information about an authorization used in a local auth list.
|
||||
* When a local auth list is put onto the charging station, the state of those authorizations is no longer tied to the actual authorization.
|
||||
* Example: A charger receives a local auth list with Authorization id = 1 in it, but then Authorization id = 1 is deleted.
|
||||
* Authorization id = 1 is still on the auth list and must be returned when upstream systems check the state of the auth list for that station, until a SendLocalListRequest removing it is successfully processed.
|
||||
* To facilitate that, this collection exists to reflect the state of Authorizations as they exist on charging stations' local auth lists.
|
||||
* In turn, the 'authorization' relation on this table links back to the "actual" authorization.
|
||||
*
|
||||
**/
|
||||
@Table // implements the same as Authorization, not OCPP2_0_1.AuthorizationData
|
||||
export class LocalListAuthorization extends Model implements AuthorizationRestrictions {
|
||||
static readonly MODEL_NAME: string = 'LocalListAuthorization';
|
||||
|
||||
@Column(DataType.ARRAY(DataType.STRING))
|
||||
declare allowedConnectorTypes?: string[];
|
||||
|
||||
@Column(DataType.ARRAY(DataType.STRING))
|
||||
declare disallowedEvseIdPrefixes?: string[];
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare idToken: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare idTokenType?: string | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare additionalInfo?: any | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare status: string;
|
||||
|
||||
@Column(DataType.DATE)
|
||||
declare cacheExpiryDateTime?: string | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare chargingPriority?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare language1?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare language2?: string | null;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare personalMessage?: any | null;
|
||||
|
||||
@ForeignKey(() => Authorization)
|
||||
@Column(DataType.INTEGER)
|
||||
declare groupAuthorizationId?: number | null;
|
||||
|
||||
@BelongsTo(() => Authorization, { foreignKey: 'groupAuthorizationId', as: 'groupAuth' })
|
||||
declare groupAuthorization?: AuthorizationDto;
|
||||
|
||||
@ForeignKey(() => Authorization)
|
||||
@Column(DataType.INTEGER)
|
||||
declare authorizationId?: string;
|
||||
|
||||
@BelongsTo(() => Authorization, { foreignKey: 'authorizationId', as: 'authorization' })
|
||||
declare authorization?: AuthorizationDto;
|
||||
|
||||
@BelongsToMany(() => SendLocalList, () => SendLocalListAuthorization)
|
||||
declare sendLocalLists?: SendLocalListDto[];
|
||||
|
||||
@BelongsToMany(() => LocalListVersion, () => LocalListVersionAuthorization)
|
||||
declare localListVersions?: LocalListVersionDto[];
|
||||
|
||||
declare customData?: any | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: LocalListAuthorization) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import type { TenantDto, LocalListAuthorizationDto } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { LocalListAuthorization } from './LocalListAuthorization.js';
|
||||
import { LocalListVersionAuthorization } from './LocalListVersionAuthorization.js';
|
||||
|
||||
@Table
|
||||
export class LocalListVersion extends Model {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.LocalListVersion;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationName_tenantId',
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare versionNumber: number;
|
||||
|
||||
@BelongsToMany(() => LocalListAuthorization, () => LocalListVersionAuthorization)
|
||||
declare localAuthorizationList?:
|
||||
| [LocalListAuthorizationDto, ...LocalListAuthorizationDto[]]
|
||||
| undefined;
|
||||
|
||||
customData?: OCPP2_0_1.CustomDataType | null | undefined;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: LocalListVersion) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { LocalListAuthorization, LocalListVersion } from './index.js';
|
||||
|
||||
@Table
|
||||
export class LocalListVersionAuthorization extends Model {
|
||||
// Namespace enum not used as this is not a model required by CitrineOS
|
||||
static readonly MODEL_NAME: string = 'LocalListVersionAuthorization';
|
||||
|
||||
@ForeignKey(() => LocalListVersion)
|
||||
@Column(DataType.INTEGER)
|
||||
declare localListVersionId: number;
|
||||
|
||||
@ForeignKey(() => LocalListAuthorization)
|
||||
@Column(DataType.INTEGER)
|
||||
declare authorizationId: number;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: LocalListVersionAuthorization) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import type { TenantDto, LocalListAuthorizationDto } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { LocalListAuthorization } from './LocalListAuthorization.js';
|
||||
import { SendLocalListAuthorization } from './SendLocalListAuthorization.js';
|
||||
|
||||
@Table
|
||||
export class SendLocalList extends Model implements OCPP2_0_1.SendLocalListRequest {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.SendLocalListRequest;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare correlationId: string;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare versionNumber: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare updateType: OCPP2_0_1.UpdateEnumType;
|
||||
|
||||
// ORM relation: LocalListAuthorization[]; API contract: AuthorizationData[]
|
||||
@BelongsToMany(() => LocalListAuthorization, () => SendLocalListAuthorization)
|
||||
declare localAuthorizationList?: any;
|
||||
|
||||
customData?: OCPP2_0_1.CustomDataType | null | undefined;
|
||||
|
||||
toSendLocalListRequest(): OCPP2_0_1.SendLocalListRequest {
|
||||
const localAuthList = (this.localAuthorizationList || [])
|
||||
.map((localListAuth: LocalListAuthorizationDto) => {
|
||||
return {
|
||||
idToken: {
|
||||
idToken: String(localListAuth.idToken), // ensure string
|
||||
type: localListAuth.idTokenType,
|
||||
additionalInfo: localListAuth.additionalInfo,
|
||||
},
|
||||
idTokenInfo: {
|
||||
status: localListAuth.status,
|
||||
cacheExpiryDateTime: localListAuth.cacheExpiryDateTime,
|
||||
chargingPriority: localListAuth.chargingPriority,
|
||||
language1: localListAuth.language1,
|
||||
groupIdToken: localListAuth.groupAuthorizationId,
|
||||
language2: localListAuth.language2,
|
||||
personalMessage: localListAuth.personalMessage,
|
||||
},
|
||||
} as OCPP2_0_1.AuthorizationData;
|
||||
})
|
||||
.filter(Boolean);
|
||||
return {
|
||||
versionNumber: this.versionNumber,
|
||||
updateType: this.updateType,
|
||||
localAuthorizationList:
|
||||
localAuthList.length > 0
|
||||
? (localAuthList as [OCPP2_0_1.AuthorizationData, ...OCPP2_0_1.AuthorizationData[]])
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: SendLocalList) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { LocalListAuthorization, SendLocalList } from './index.js';
|
||||
|
||||
@Table
|
||||
export class SendLocalListAuthorization extends Model {
|
||||
// Namespace enum not used as this is not a model required by CitrineOS
|
||||
static readonly MODEL_NAME: string = 'SendLocalListAuthorization';
|
||||
|
||||
@ForeignKey(() => SendLocalList)
|
||||
@Column(DataType.INTEGER)
|
||||
declare sendLocalListId: number;
|
||||
|
||||
@ForeignKey(() => LocalListAuthorization)
|
||||
@Column(DataType.INTEGER)
|
||||
declare authorizationId: number;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: SendLocalListAuthorization) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { Authorization } from './Authorization.js';
|
||||
export { LocalListVersion } from './LocalListVersion.js';
|
||||
export { SendLocalList } from './SendLocalList.js';
|
||||
export { LocalListAuthorization } from './LocalListAuthorization.js';
|
||||
export { LocalListVersionAuthorization } from './LocalListVersionAuthorization.js';
|
||||
export { SendLocalListAuthorization } from './SendLocalListAuthorization.js';
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Internal types for Authorization models to break circular dependencies
|
||||
|
||||
export interface ITenant {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ITenantPartner {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import type { TenantDto } from '@citrineos/base';
|
||||
import { BeforeCreate, BeforeUpdate, Column, DataType, Model } from 'sequelize-typescript';
|
||||
|
||||
export abstract class BaseModelWithTenant<
|
||||
TModelAttributes extends {} = any,
|
||||
TCreationAttributes extends {} = TModelAttributes,
|
||||
> extends Model<TModelAttributes, TCreationAttributes> {
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE', // update tenantId if the tenant primary key is updated (should never happen)
|
||||
onDelete: 'RESTRICT', // ensure tenant row cannot be deleted if there are existing records using it
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: BaseModelWithTenant) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BootDto, TenantDto, VariableAttributeDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { VariableAttribute } from './DeviceModel/VariableAttribute.js';
|
||||
import { Tenant } from './Tenant.js';
|
||||
|
||||
@Table
|
||||
export class Boot extends Model implements BootDto {
|
||||
static readonly MODEL_NAME: string = Namespace.BootConfig;
|
||||
|
||||
/**
|
||||
* StationId
|
||||
*/
|
||||
@PrimaryKey
|
||||
@Column(DataType.STRING)
|
||||
declare id: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const lastBootTimeValue = this.getDataValue('lastBootTime');
|
||||
return lastBootTimeValue ? lastBootTimeValue.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare lastBootTime?: string | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare heartbeatInterval?: number | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare bootRetryInterval?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare status: string;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare statusInfo?: object | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare getBaseReportOnPending?: boolean | null;
|
||||
|
||||
/**
|
||||
* Variable attributes to be sent in SetVariablesRequest on pending boot
|
||||
*/
|
||||
@HasMany(() => VariableAttribute, 'bootConfigId')
|
||||
declare pendingBootSetVariables?: VariableAttributeDto[];
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare variablesRejectedOnLastBoot?: object[] | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare bootWithRejectedVariables?: boolean | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare changeConfigurationsOnPending?: boolean | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare getConfigurationsOnPending?: boolean | null;
|
||||
|
||||
declare customData?: object | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Boot) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { CertificateDto, CountryName, SignatureAlgorithm, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table({
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['tenantId', 'serialNumber', 'issuerName'],
|
||||
name: 'tenantId_serialNumber_issuerName',
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
fields: ['tenantId', 'certificateFileHash'],
|
||||
name: 'tenantId_certificateFileHash',
|
||||
},
|
||||
],
|
||||
})
|
||||
export class Certificate extends Model implements CertificateDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.Certificate;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
// use serialNumber and issuerName as unique constraint based on 4.1.2.2 in https://www.rfc-editor.org/rfc/rfc5280
|
||||
@Column(DataType.BIGINT)
|
||||
declare serialNumber: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare issuerName: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare organizationName: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare commonName: string;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare keyLength?: number | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const validBefore: Date = this.getDataValue('validBefore');
|
||||
return validBefore ? validBefore.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare validBefore?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare signatureAlgorithm?: SignatureAlgorithm | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare countryName?: CountryName | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare isCA?: boolean;
|
||||
|
||||
// A pathLenConstraint of zero indicates that no intermediate CA certificates may
|
||||
// follow in a valid certification path. Where it appears, the pathLenConstraint field MUST be greater than or
|
||||
// equal to zero. Where pathLenConstraint does not appear, no limit is imposed.
|
||||
// Reference: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9
|
||||
@Column(DataType.INTEGER)
|
||||
declare pathLen?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare certificateFileId?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare certificateFileHash?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare privateKeyFileId?: string | null;
|
||||
|
||||
@ForeignKey(() => Certificate)
|
||||
@Column(DataType.INTEGER)
|
||||
declare signedBy?: number | null; // certificate id
|
||||
|
||||
@BelongsTo(() => Certificate, { foreignKey: 'signedBy', as: 'signingCertificate' })
|
||||
declare signingCertificate?: Certificate;
|
||||
|
||||
@HasMany(() => Certificate, { foreignKey: 'signedBy', as: 'signedCertificates' })
|
||||
declare signedCertificates?: Certificate[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Certificate) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export const enum SignatureAlgorithmEnumType {
|
||||
RSA = 'SHA256withRSA',
|
||||
ECDSA = 'SHA256withECDSA',
|
||||
}
|
||||
|
||||
export const enum CountryNameEnumType {
|
||||
US = 'US',
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
OCPP2_Namespace,
|
||||
type DeleteCertificateStatusEnumType,
|
||||
type HashAlgorithmEnumType,
|
||||
type TenantDto,
|
||||
type ChargingStationDto,
|
||||
} from '@citrineos/base';
|
||||
import { ChargingStation } from '../Location/index.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class DeleteCertificateAttempt extends Model {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.DeleteCertificateAttempt;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column(DataType.INTEGER)
|
||||
declare stationId?: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(36),
|
||||
allowNull: false,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare station?: ChargingStationDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: false,
|
||||
})
|
||||
declare hashAlgorithm: HashAlgorithmEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare issuerNameHash: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare issuerKeyHash: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare serialNumber: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
})
|
||||
declare status?: DeleteCertificateStatusEnumType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: DeleteCertificateAttempt): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: DeleteCertificateAttempt) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
OCPP2_Namespace,
|
||||
type CertificateDto,
|
||||
type CertificateUseEnumType,
|
||||
type ChargingStationDto,
|
||||
type InstallCertificateStatusEnumType,
|
||||
type TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from '../Location/index.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Certificate } from './Certificate.js';
|
||||
|
||||
@Table
|
||||
export class InstallCertificateAttempt extends Model {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.InstallCertificateAttempt;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column(DataType.INTEGER)
|
||||
declare stationId?: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(36),
|
||||
allowNull: false,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare station?: ChargingStationDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: false,
|
||||
})
|
||||
declare certificateType: CertificateUseEnumType;
|
||||
|
||||
@ForeignKey(() => Certificate)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
declare certificateId: number;
|
||||
|
||||
@BelongsTo(() => Certificate, 'certificateId')
|
||||
declare certificate?: CertificateDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: true,
|
||||
})
|
||||
declare requestId?: number | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
})
|
||||
declare status?: InstallCertificateStatusEnumType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: InstallCertificateAttempt): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: InstallCertificateAttempt) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
CertificateDto,
|
||||
CertificateUseEnumType,
|
||||
HashAlgorithmEnumType,
|
||||
InstalledCertificateDto,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_Namespace, type ChargingStationDto } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
|
||||
import { ChargingStation } from '../Location/index.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Certificate } from './Certificate.js';
|
||||
|
||||
@Table
|
||||
export class InstalledCertificate extends Model implements InstalledCertificateDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.InstalledCertificate;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: true,
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(36),
|
||||
allowNull: false,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: true,
|
||||
})
|
||||
declare hashAlgorithm: HashAlgorithmEnumType;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: true,
|
||||
})
|
||||
declare issuerNameHash?: string | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: true,
|
||||
})
|
||||
declare issuerKeyHash?: string | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: true,
|
||||
})
|
||||
declare serialNumber?: string | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: false,
|
||||
})
|
||||
declare certificateType: CertificateUseEnumType;
|
||||
|
||||
@ForeignKey(() => Certificate)
|
||||
@Column(DataType.INTEGER)
|
||||
declare certificateId?: number | null;
|
||||
|
||||
@BelongsTo(() => Certificate, 'certificateId')
|
||||
declare certificate?: CertificateDto;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare station?: ChargingStationDto;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: InstalledCertificate): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: InstalledCertificate) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { Certificate } from './Certificate.js';
|
||||
export { InstalledCertificate } from './InstalledCertificate.js';
|
||||
export { InstallCertificateAttempt } from './InstallCertificateAttempt.js';
|
||||
export { DeleteCertificateAttempt } from './DeleteCertificateAttempt.js';
|
||||
export { SignatureAlgorithmEnumType, CountryNameEnumType } from './CertificateTypes.js';
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { DEFAULT_TENANT_ID, OCPP1_6_Namespace } from '@citrineos/base';
|
||||
import type { ChangeConfigurationDto, TenantDto } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from './Tenant.js';
|
||||
|
||||
@Table
|
||||
export class ChangeConfiguration extends Model implements ChangeConfigurationDto {
|
||||
static readonly MODEL_NAME: string = OCPP1_6_Namespace.ChangeConfiguration;
|
||||
|
||||
@Column({
|
||||
unique: 'stationName_tenantId_key',
|
||||
allowNull: false,
|
||||
type: DataType.STRING,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
unique: 'stationName_tenantId_key',
|
||||
allowNull: false,
|
||||
type: DataType.STRING(50),
|
||||
})
|
||||
declare key: string;
|
||||
|
||||
@Column(DataType.STRING(500))
|
||||
declare value?: string | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare readonly?: boolean | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId_key',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChangeConfiguration) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ACChargingParametersType,
|
||||
ChargingNeedsDto,
|
||||
DCChargingParametersType,
|
||||
EnergyTransferModeEnumType,
|
||||
EvseDto,
|
||||
TenantDto,
|
||||
TransactionDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Evse } from '../Location/index.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
|
||||
@Table
|
||||
export class ChargingNeeds extends Model implements ChargingNeedsDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.ChargingNeeds;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
@Column(DataType.JSONB)
|
||||
declare acChargingParameters?: ACChargingParametersType | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare dcChargingParameters?: DCChargingParametersType | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const departureTime: Date = this.getDataValue('departureTime');
|
||||
return departureTime ? departureTime.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare departureTime?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare requestedEnergyTransfer: EnergyTransferModeEnumType;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare maxScheduleTuples?: number | null;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
@ForeignKey(() => Evse)
|
||||
@Column(DataType.INTEGER)
|
||||
declare evseId: number;
|
||||
|
||||
@BelongsTo(() => Evse, 'evseId')
|
||||
declare evse: EvseDto;
|
||||
|
||||
@ForeignKey(() => Transaction)
|
||||
@Column(DataType.INTEGER)
|
||||
declare transactionDatabaseId: number;
|
||||
|
||||
@BelongsTo(() => Transaction, 'transactionDatabaseId')
|
||||
declare transaction: TransactionDto;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChargingNeeds) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingLimitSourceEnumType,
|
||||
ChargingProfileDto,
|
||||
ChargingProfileKindEnumType,
|
||||
ChargingProfilePurposeEnumType,
|
||||
ChargingScheduleDto,
|
||||
RecurrencyKindEnumType,
|
||||
TenantDto,
|
||||
TransactionDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
import { ChargingSchedule } from './ChargingSchedule.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class ChargingProfile extends Model implements ChargingProfileDto {
|
||||
static readonly MODEL_NAME: string = Namespace.ChargingProfile;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column(DataType.INTEGER)
|
||||
declare databaseId: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare chargingProfileKind: ChargingProfileKindEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare chargingProfilePurpose: ChargingProfilePurposeEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare recurrencyKind?: RecurrencyKindEnumType | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare stackLevel: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const validFrom: Date = this.getDataValue('validFrom');
|
||||
return validFrom ? validFrom.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare validFrom?: string | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const validTo: Date = this.getDataValue('validTo');
|
||||
return validTo ? validTo.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare validTo?: string | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare evseId?: number | null;
|
||||
|
||||
// this value indicates whether the ChargingProfile is set on charger
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare isActive: boolean;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: 'CSO',
|
||||
})
|
||||
declare chargingLimitSource?: ChargingLimitSourceEnumType | null;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
@HasMany(() => ChargingSchedule, 'chargingProfileDatabaseId')
|
||||
declare chargingSchedule:
|
||||
| [ChargingScheduleDto]
|
||||
| [ChargingScheduleDto, ChargingScheduleDto]
|
||||
| [ChargingScheduleDto, ChargingScheduleDto, ChargingScheduleDto];
|
||||
|
||||
@ForeignKey(() => Transaction)
|
||||
declare transactionDatabaseId?: number | null;
|
||||
|
||||
@BelongsTo(() => Transaction, 'transactionDatabaseId')
|
||||
declare transaction?: TransactionDto;
|
||||
|
||||
declare customData?: object | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChargingProfile) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingProfileDto,
|
||||
ChargingRateUnitEnumType,
|
||||
ChargingScheduleDto,
|
||||
SalesTariffDto,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { ChargingProfile } from './ChargingProfile.js';
|
||||
import { SalesTariff } from './SalesTariff.js';
|
||||
|
||||
@Table
|
||||
export class ChargingSchedule extends Model implements ChargingScheduleDto {
|
||||
static readonly MODEL_NAME: string = Namespace.ChargingSchedule;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column(DataType.INTEGER)
|
||||
declare databaseId: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare chargingRateUnit: ChargingRateUnitEnumType;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare chargingSchedulePeriod: [any, ...any[]];
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare duration?: number | null;
|
||||
|
||||
@Column(DataType.DECIMAL)
|
||||
declare minChargingRate?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare startSchedule?: string | null;
|
||||
|
||||
// Periods contained in the charging profile are relative to this point in time.
|
||||
// From NotifyEVChargingScheduleRequest
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const timeBase: Date = this.getDataValue('timeBase');
|
||||
return timeBase ? timeBase.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare timeBase?: string;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
@BelongsTo(() => ChargingProfile, 'chargingProfileDatabaseId')
|
||||
declare chargingProfile: ChargingProfileDto;
|
||||
|
||||
@ForeignKey(() => ChargingProfile)
|
||||
@Column(DataType.INTEGER)
|
||||
declare chargingProfileDatabaseId?: number;
|
||||
|
||||
@ForeignKey(() => SalesTariff)
|
||||
declare salesTariffId?: number | null;
|
||||
|
||||
@BelongsTo(() => SalesTariff, 'salesTariffId')
|
||||
declare salesTariff?: SalesTariffDto;
|
||||
|
||||
@HasMany(() => SalesTariff, 'chargingScheduleDatabaseId')
|
||||
declare salesTariffs?: SalesTariffDto[];
|
||||
|
||||
declare customData?: object | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChargingSchedule) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { CompositeScheduleDto, EvseDto, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Evse } from '../Location/index.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class CompositeSchedule extends Model implements CompositeScheduleDto {
|
||||
static readonly MODEL_NAME: string = Namespace.CompositeSchedule;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@ForeignKey(() => Evse)
|
||||
@Column(DataType.INTEGER)
|
||||
declare evseId: number;
|
||||
|
||||
@BelongsTo(() => Evse, 'evseId')
|
||||
declare evse?: EvseDto;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare duration: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const scheduleStart: Date = this.getDataValue('scheduleStart');
|
||||
return scheduleStart ? scheduleStart.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare scheduleStart: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare chargingRateUnit: string;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare chargingSchedulePeriod: [object, ...object[]];
|
||||
|
||||
declare customData?: object | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: CompositeSchedule) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingScheduleDto,
|
||||
SalesTariffDto,
|
||||
SalesTariffEntry,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { ChargingSchedule } from './ChargingSchedule.js';
|
||||
|
||||
@Table
|
||||
export class SalesTariff extends Model implements SalesTariffDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.SalesTariff;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column(DataType.INTEGER)
|
||||
declare databaseId: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'id_chargingScheduleDatabaseId',
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare numEPriceLevels?: number | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare salesTariffDescription?: string | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare salesTariffEntry: [SalesTariffEntry, ...SalesTariffEntry[]];
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
@ForeignKey(() => ChargingSchedule)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'id_chargingScheduleDatabaseId',
|
||||
})
|
||||
declare chargingScheduleDatabaseId: number;
|
||||
|
||||
@BelongsTo(() => ChargingSchedule, 'chargingScheduleDatabaseId')
|
||||
declare chargingSchedule?: ChargingScheduleDto;
|
||||
|
||||
@HasMany(() => ChargingSchedule, 'salesTariffId')
|
||||
declare chargingSchedulesBySalesTariff?: ChargingScheduleDto[];
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: SalesTariff) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { ChargingProfile } from './ChargingProfile.js';
|
||||
export { ChargingNeeds } from './ChargingNeeds.js';
|
||||
export { ChargingSchedule } from './ChargingSchedule.js';
|
||||
export { SalesTariff } from './SalesTariff.js';
|
||||
export { CompositeSchedule } from './CompositeSchedule.js';
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { DEFAULT_TENANT_ID, OCPP2_Namespace } from '@citrineos/base';
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
ChargingStationSecurityInfoDto,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from './Location/index.js';
|
||||
import { Tenant } from './Tenant.js';
|
||||
|
||||
/**
|
||||
* Represents the security information found on a particular charging station.
|
||||
*/
|
||||
@Table
|
||||
export class ChargingStationSecurityInfo extends Model implements ChargingStationSecurityInfoDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.ChargingStationSecurityInfo;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column(DataType.INTEGER)
|
||||
declare stationId?: number;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation?: ChargingStationDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationName_tenantId',
|
||||
})
|
||||
ocppConnectionName!: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
publicKeyFileId!: string;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: ChargingStationSecurityInfo): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const { ChargingStation } = await import('./Location/ChargingStation.js');
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChargingStationSecurityInfo) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { ChargingStationSequenceTypeEnumType, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from '../Location/index.js';
|
||||
import type { ChargingStation as ChargingStationType } from '../Location/index.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class ChargingStationSequence extends Model {
|
||||
static readonly MODEL_NAME: string = 'ChargingStationSequence';
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: true,
|
||||
unique: 'stationId_type',
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(36),
|
||||
allowNull: false,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: false,
|
||||
unique: 'stationId_type',
|
||||
})
|
||||
type!: ChargingStationSequenceTypeEnumType;
|
||||
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
})
|
||||
value!: number;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare station: ChargingStationType;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: ChargingStationSequence): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChargingStationSequence) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export { ChargingStationSequence } from './ChargingStationSequence.js';
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type {
|
||||
ComponentDto,
|
||||
MessageInfoDto,
|
||||
TenantDto,
|
||||
VariableAttributeDto,
|
||||
VariableDto,
|
||||
VariableMonitoringDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
|
||||
import { MessageInfo } from '../MessageInfo/MessageInfo.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { VariableMonitoring } from '../VariableMonitoring/VariableMonitoring.js';
|
||||
import { ComponentVariable } from './ComponentVariable.js';
|
||||
import { EvseType } from './EvseType.js';
|
||||
import { Variable } from './Variable.js';
|
||||
import { VariableAttribute } from './VariableAttribute.js';
|
||||
|
||||
@Table({
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
name: 'components_tenantId_name',
|
||||
fields: ['tenantId', 'name'],
|
||||
where: {
|
||||
instance: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class Component extends Model implements OCPP2_0_1.ComponentType, ComponentDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.ComponentType;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'tenantId_name_instance',
|
||||
})
|
||||
declare name: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'tenantId_name_instance',
|
||||
})
|
||||
declare instance?: string | null;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
@BelongsTo(() => EvseType, 'evseDatabaseId')
|
||||
declare evse?: EvseType;
|
||||
|
||||
@ForeignKey(() => EvseType)
|
||||
@Column(DataType.INTEGER)
|
||||
declare evseDatabaseId?: number | null;
|
||||
|
||||
@BelongsToMany(() => Variable, { through: () => ComponentVariable, foreignKey: 'componentId' })
|
||||
declare variables?: VariableDto[];
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
// Declare the association methods, to be automatically generated by Sequelize at runtime
|
||||
public addVariable!: (variable: VariableDto) => Promise<void>;
|
||||
public getVariables!: () => Promise<VariableDto[]>;
|
||||
|
||||
@HasMany(() => VariableAttribute, 'componentId')
|
||||
declare variableAttributes?: VariableAttributeDto[];
|
||||
|
||||
@HasMany(() => VariableMonitoring, 'componentId')
|
||||
declare variableMonitorings?: VariableMonitoringDto[];
|
||||
|
||||
@HasMany(() => MessageInfo, 'displayComponentId')
|
||||
declare messageInfos?: MessageInfoDto[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
unique: 'tenantId_name_instance',
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Component) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Component, Variable } from './index.js';
|
||||
|
||||
@Table
|
||||
export class ComponentVariable extends Model {
|
||||
// Namespace enum not used as this is not a model required by CitrineOS
|
||||
static readonly MODEL_NAME: string = 'ComponentVariable';
|
||||
|
||||
@ForeignKey(() => Component)
|
||||
@Column(DataType.INTEGER)
|
||||
declare componentId: number;
|
||||
|
||||
@ForeignKey(() => Variable)
|
||||
@Column(DataType.INTEGER)
|
||||
declare variableId: number;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ComponentVariable) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { EvseTypeDto, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table({
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
name: 'evse_types_tenantId_id',
|
||||
fields: ['tenantId', 'id'],
|
||||
where: {
|
||||
connectorId: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class EvseType extends Model implements OCPP2_0_1.EVSEType, EvseTypeDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.EVSEType;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column(DataType.INTEGER)
|
||||
declare databaseId: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'tenantId_id_connectorId',
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'tenantId_id_connectorId',
|
||||
})
|
||||
declare connectorId?: number | null;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
unique: 'tenantId_id_connectorId',
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: EvseType) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ComponentDto,
|
||||
TenantDto,
|
||||
VariableAttributeDto,
|
||||
VariableCharacteristicsDto,
|
||||
VariableDto,
|
||||
VariableMonitoringDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
HasOne,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { VariableMonitoring } from '../VariableMonitoring/VariableMonitoring.js';
|
||||
import { Component } from './Component.js';
|
||||
import { ComponentVariable } from './ComponentVariable.js';
|
||||
import { VariableAttribute } from './VariableAttribute.js';
|
||||
import { VariableCharacteristics } from './VariableCharacteristics.js';
|
||||
|
||||
@Table({
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
name: 'variables_tenantId_name',
|
||||
fields: ['tenantId', 'name'],
|
||||
where: {
|
||||
instance: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class Variable extends Model implements OCPP2_0_1.VariableType, VariableDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.VariableType;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'tenantId_name_instance',
|
||||
})
|
||||
declare name: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'tenantId_name_instance',
|
||||
})
|
||||
declare instance?: string | null;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
@BelongsToMany(() => Component, { through: () => ComponentVariable, foreignKey: 'variableId' })
|
||||
declare components?: ComponentDto[];
|
||||
|
||||
@HasMany(() => VariableAttribute, 'variableId')
|
||||
declare variableAttributes?: VariableAttributeDto[];
|
||||
|
||||
@HasOne(() => VariableCharacteristics, 'variableId')
|
||||
declare variableCharacteristics?: VariableCharacteristicsDto;
|
||||
|
||||
@HasMany(() => VariableMonitoring, 'variableId')
|
||||
declare variableMonitorings?: VariableMonitoringDto[];
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
// Declare the association methods, to be automatically generated by Sequelize at runtime
|
||||
public addComponent!: (variable: ComponentDto) => Promise<void>;
|
||||
public getComponents!: () => Promise<ComponentDto[]>;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
unique: 'tenantId_name_instance',
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Variable) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
BootDto,
|
||||
ChargingStationDto,
|
||||
ComponentDto,
|
||||
EvseTypeDto,
|
||||
TenantDto,
|
||||
VariableAttributeDto,
|
||||
VariableDto,
|
||||
VariableStatusDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Index,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { CryptoUtils } from '../../../../util/CryptoUtils.js';
|
||||
|
||||
import { ChargingStation } from '../Location/index.js';
|
||||
|
||||
import { Boot } from '../Boot.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Component } from './Component.js';
|
||||
import { EvseType } from './EvseType.js';
|
||||
import { Variable } from './Variable.js';
|
||||
import { VariableStatus } from './VariableStatus.js';
|
||||
|
||||
@Table({
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId',
|
||||
fields: ['stationId'],
|
||||
where: {
|
||||
type: null,
|
||||
variableId: null,
|
||||
componentId: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId_type',
|
||||
fields: ['stationId', 'type'],
|
||||
where: {
|
||||
variableId: null,
|
||||
componentId: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId_variableId',
|
||||
fields: ['stationId', 'variableId'],
|
||||
where: {
|
||||
type: null,
|
||||
componentId: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId_componentId',
|
||||
fields: ['stationId', 'componentId'],
|
||||
where: {
|
||||
type: null,
|
||||
variableId: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId_type_variableId',
|
||||
fields: ['stationId', 'type', 'variableId'],
|
||||
where: {
|
||||
componentId: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId_type_componentId',
|
||||
fields: ['stationId', 'type', 'componentId'],
|
||||
where: {
|
||||
variableId: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
name: 'variable_attributes_stationId_variableId_componentId',
|
||||
fields: ['stationId', 'variableId', 'componentId'],
|
||||
where: {
|
||||
type: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class VariableAttribute
|
||||
extends Model
|
||||
implements OCPP2_0_1.VariableAttributeType, VariableAttributeDto
|
||||
{
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.VariableAttributeType;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_type_variableId_componentId',
|
||||
allowNull: true,
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@Index
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: false,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation: ChargingStationDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: OCPP2_0_1.AttributeEnumType.Actual,
|
||||
unique: 'stationId_type_variableId_componentId',
|
||||
})
|
||||
declare type?: OCPP2_0_1.AttributeEnumType | null;
|
||||
|
||||
// From VariableCharacteristics, which belongs to Variable associated with this VariableAttribute
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: OCPP2_0_1.DataEnumType.string,
|
||||
})
|
||||
declare dataType: OCPP2_0_1.DataEnumType;
|
||||
|
||||
@Column({
|
||||
// TODO: Make this configurable? also used in VariableStatus model
|
||||
type: DataType.STRING(4000),
|
||||
set(valueString: string) {
|
||||
if (valueString) {
|
||||
const valueType = (this as VariableAttribute).dataType;
|
||||
switch (valueType) {
|
||||
case OCPP2_0_1.DataEnumType.passwordString:
|
||||
valueString = CryptoUtils.getPasswordHash(valueString);
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setDataValue('value', valueString);
|
||||
},
|
||||
})
|
||||
declare value?: string | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: OCPP2_0_1.MutabilityEnumType.ReadWrite,
|
||||
})
|
||||
declare mutability?: OCPP2_0_1.MutabilityEnumType | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare persistent?: boolean | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare constant?: boolean | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
return this.getDataValue('generatedAt').toISOString();
|
||||
},
|
||||
})
|
||||
declare generatedAt: string;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
@BelongsTo(() => Variable, 'variableId')
|
||||
declare variable: VariableDto;
|
||||
|
||||
@ForeignKey(() => Variable)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_type_variableId_componentId',
|
||||
})
|
||||
declare variableId?: number | null;
|
||||
|
||||
@BelongsTo(() => Component, 'componentId')
|
||||
declare component: ComponentDto;
|
||||
|
||||
@ForeignKey(() => Component)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_type_variableId_componentId',
|
||||
})
|
||||
declare componentId?: number | null;
|
||||
|
||||
@BelongsTo(() => EvseType, 'evseDatabaseId')
|
||||
declare evse?: EvseTypeDto;
|
||||
|
||||
@ForeignKey(() => EvseType)
|
||||
@Column(DataType.INTEGER)
|
||||
declare evseDatabaseId?: number | null;
|
||||
|
||||
// History of variable status. Can be directly from GetVariablesResponse or SetVariablesResponse, or from NotifyReport handling, or from 'setOnCharger' option for data api
|
||||
|
||||
@HasMany(() => VariableStatus, 'variableAttributeId')
|
||||
declare statuses?: VariableStatusDto[];
|
||||
|
||||
// Below used to associate attributes with boot process
|
||||
|
||||
@BelongsTo(() => Boot, 'bootConfigId')
|
||||
declare bootConfig?: BootDto;
|
||||
|
||||
@ForeignKey(() => Boot)
|
||||
@Column(DataType.STRING)
|
||||
declare bootConfigId?: string | null;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: VariableAttribute): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: VariableAttribute) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { VariableCharacteristicsDto, VariableDto, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Variable } from './Variable.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class VariableCharacteristics
|
||||
extends Model
|
||||
implements OCPP2_0_1.VariableCharacteristicsType, VariableCharacteristicsDto
|
||||
{
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.VariableCharacteristicsType;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare unit?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare dataType: OCPP2_0_1.DataEnumType;
|
||||
|
||||
@Column(DataType.DECIMAL)
|
||||
declare minLimit?: number | null;
|
||||
|
||||
@Column(DataType.DECIMAL)
|
||||
declare maxLimit?: number | null;
|
||||
|
||||
@Column(DataType.STRING(4000))
|
||||
declare valuesList?: string | null;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare supportsMonitoring: boolean;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
@BelongsTo(() => Variable, 'variableId')
|
||||
declare variable: VariableDto;
|
||||
|
||||
@ForeignKey(() => Variable)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: true,
|
||||
})
|
||||
declare variableId?: number | null;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: VariableCharacteristics) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
VariableAttributeDto,
|
||||
VariableStatusDto,
|
||||
TenantDto,
|
||||
StatusInfo,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { VariableAttribute } from './VariableAttribute.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class VariableStatus extends Model implements VariableStatusDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.VariableStatus;
|
||||
|
||||
@Column(DataType.STRING(4000))
|
||||
declare value: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare status: string;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare statusInfo?: StatusInfo | null;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
@BelongsTo(() => VariableAttribute, 'variableAttributeId')
|
||||
declare variable: VariableAttributeDto;
|
||||
|
||||
@ForeignKey(() => VariableAttribute)
|
||||
@Column(DataType.INTEGER)
|
||||
declare variableAttributeId?: number | null;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: VariableStatus) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { Component } from './Component.js';
|
||||
export { ComponentVariable } from './ComponentVariable.js';
|
||||
export { EvseType } from './EvseType.js';
|
||||
export { Variable } from './Variable.js';
|
||||
export { VariableAttribute } from './VariableAttribute.js';
|
||||
export { VariableCharacteristics } from './VariableCharacteristics.js';
|
||||
export { VariableStatus } from './VariableStatus.js';
|
||||
@@ -0,0 +1,221 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingStationCapabilityEnumType,
|
||||
ChargingStationDto,
|
||||
ChargingStationParkingRestrictionEnumType,
|
||||
ChargingStationSecurityInfoDto,
|
||||
ChargingStationSequenceDto,
|
||||
ConnectorDto,
|
||||
EvseDto,
|
||||
InstalledCertificateDto,
|
||||
LocationDto,
|
||||
OCPPMessageDto,
|
||||
Point,
|
||||
ServerNetworkProfileDto,
|
||||
StatusNotificationDto,
|
||||
TenantDto,
|
||||
VariableAttributeDto,
|
||||
VariableMonitoringDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace, OCPPVersion } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Index,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
|
||||
import { DeleteCertificateAttempt } from '../Certificate/DeleteCertificateAttempt.js';
|
||||
import { InstalledCertificate } from '../Certificate/InstalledCertificate.js';
|
||||
import { ChargingStationSecurityInfo } from '../ChargingStationSecurityInfo.js';
|
||||
import { ChargingStationSequence } from '../ChargingStationSequence/ChargingStationSequence.js';
|
||||
import { VariableAttribute } from '../DeviceModel/VariableAttribute.js';
|
||||
import { OCPPMessage } from '../OCPPMessage.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
import { EventData } from '../VariableMonitoring/EventData.js';
|
||||
import { VariableMonitoring } from '../VariableMonitoring/VariableMonitoring.js';
|
||||
import { ChargingStationNetworkProfile } from './ChargingStationNetworkProfile.js';
|
||||
import { Connector } from './Connector.js';
|
||||
import { Evse } from './Evse.js';
|
||||
import { Location } from './Location.js';
|
||||
import { ServerNetworkProfile } from './ServerNetworkProfile.js';
|
||||
import { StatusNotification } from './StatusNotification.js';
|
||||
|
||||
/**
|
||||
* Represents a charging station.
|
||||
* Currently, this data model is internal to CitrineOS. In the future, it will be analogous to an OCPI ChargingStation.
|
||||
*/
|
||||
@Table
|
||||
export class ChargingStation extends Model implements ChargingStationDto {
|
||||
static readonly MODEL_NAME: string = Namespace.ChargingStation;
|
||||
|
||||
@AutoIncrement
|
||||
@PrimaryKey
|
||||
@Column(DataType.INTEGER)
|
||||
declare id: number;
|
||||
|
||||
/**
|
||||
* The tenant-scoped charging station identifier — used in WebSocket routing
|
||||
* (the charger appends this to the end of the WebSocket URL on connect).
|
||||
* Unique per tenant, but two different tenants may share the same value.
|
||||
*/
|
||||
@Index
|
||||
@Column({
|
||||
type: DataType.STRING(36),
|
||||
unique: 'ChargingStations_stationName_tenantId_key',
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare isOnline: boolean;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare protocol?: OCPPVersion | null;
|
||||
|
||||
@Column(DataType.DATE)
|
||||
declare latestOcppMessageTimestamp?: string | null;
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare chargePointVendor?: string | null;
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare chargePointModel?: string | null;
|
||||
|
||||
@Column(DataType.STRING(25))
|
||||
declare chargePointSerialNumber?: string | null;
|
||||
|
||||
@Column(DataType.STRING(25))
|
||||
declare chargeBoxSerialNumber?: string | null;
|
||||
|
||||
@Column(DataType.STRING(50))
|
||||
declare firmwareVersion?: string | null;
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare iccid?: string | null;
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare imsi?: string | null;
|
||||
|
||||
@Column(DataType.STRING(25))
|
||||
declare meterType?: string | null;
|
||||
|
||||
@Column(DataType.STRING(25))
|
||||
declare meterSerialNumber?: string | null;
|
||||
|
||||
/**
|
||||
* [longitude, latitude]
|
||||
*/
|
||||
@Column(DataType.GEOMETRY('POINT'))
|
||||
declare coordinates?: Point | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare floorLevel?: string | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare parkingRestrictions?: ChargingStationParkingRestrictionEnumType[] | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare capabilities?: ChargingStationCapabilityEnumType[] | null;
|
||||
|
||||
/**
|
||||
* In OCPP 1.6, StatusNotifications can be sent with a connectorId of 0 to report the status of the whole charging station.
|
||||
* Some charging stations instead use it in ways that cannot be applied to all connectors
|
||||
* (such as sending Available when at least one connector is available, while others are charging).
|
||||
* When true, this flag indicates that StatusNotifications with connectorId 0 should be used to update all connector statuses.
|
||||
* When false, StatusNotifications with connectorId 0 should be ignored.
|
||||
*/
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: true,
|
||||
})
|
||||
declare use16StatusNotification0: boolean;
|
||||
|
||||
@ForeignKey(() => Location)
|
||||
@Column(DataType.INTEGER)
|
||||
declare locationId?: number | null;
|
||||
|
||||
@HasMany(() => StatusNotification, 'stationId')
|
||||
declare statusNotifications?: StatusNotificationDto[] | null;
|
||||
|
||||
@HasMany(() => InstalledCertificate, 'stationId')
|
||||
declare installedCertificates?: InstalledCertificateDto[];
|
||||
|
||||
@HasMany(() => Transaction, 'stationId')
|
||||
declare transactions?: Transaction[] | null;
|
||||
|
||||
/**
|
||||
* The business Location of the charging station. Optional in case a charging station is not yet in the field, or retired.
|
||||
*/
|
||||
@BelongsTo(() => Location, 'locationId')
|
||||
declare location?: LocationDto;
|
||||
|
||||
@BelongsToMany(() => ServerNetworkProfile, () => ChargingStationNetworkProfile)
|
||||
declare networkProfiles?: ServerNetworkProfileDto[] | null;
|
||||
|
||||
@HasMany(() => Evse, 'stationId')
|
||||
declare evses?: EvseDto[] | null;
|
||||
|
||||
@HasMany(() => Connector, 'stationId')
|
||||
declare connectors?: ConnectorDto[] | null;
|
||||
|
||||
@HasMany(() => VariableAttribute, 'stationId')
|
||||
declare variableAttributes?: VariableAttributeDto[];
|
||||
|
||||
@HasMany(() => OCPPMessage, 'stationId')
|
||||
declare ocppMessages?: OCPPMessageDto[];
|
||||
|
||||
@HasMany(() => VariableMonitoring, 'stationId')
|
||||
declare variableMonitorings?: VariableMonitoringDto[];
|
||||
|
||||
@HasMany(() => EventData, 'stationId')
|
||||
declare stationEventData?: EventData[];
|
||||
|
||||
@HasMany(() => ChargingStationSecurityInfo, 'stationId')
|
||||
declare securityInfo?: ChargingStationSecurityInfoDto[];
|
||||
|
||||
@HasMany(() => ChargingStationSequence, 'stationId')
|
||||
declare sequences?: ChargingStationSequenceDto[];
|
||||
|
||||
@HasMany(() => DeleteCertificateAttempt, 'stationId')
|
||||
declare deleteCertificateAttempts?: DeleteCertificateAttempt[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'ChargingStations_stationName_tenantId_key',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
@BeforeUpdate
|
||||
static setDefaultTenant(instance: ChargingStation) {
|
||||
if (instance.isNewRecord && instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
ChargingStationNetworkProfileDto,
|
||||
ServerNetworkProfileDto,
|
||||
SetNetworkProfileDto,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { ServerNetworkProfile } from './ServerNetworkProfile.js';
|
||||
import { SetNetworkProfile } from './SetNetworkProfile.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class ChargingStationNetworkProfile
|
||||
extends Model
|
||||
implements ChargingStationNetworkProfileDto
|
||||
{
|
||||
// Namespace enum not used as this is not a model required by CitrineOS
|
||||
static readonly MODEL_NAME: string = 'ChargingStationNetworkProfile';
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_configurationSlot',
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation?: ChargingStationDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
/**
|
||||
* Possible values for a particular station found in device model:
|
||||
* OCPPCommCtrlr.NetworkConfigurationPriority.VariableCharacteristics.valuesList
|
||||
*/
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_configurationSlot',
|
||||
})
|
||||
declare configurationSlot: number;
|
||||
|
||||
@ForeignKey(() => SetNetworkProfile)
|
||||
@Column(DataType.INTEGER)
|
||||
declare setNetworkProfileId: number;
|
||||
|
||||
@BelongsTo(() => SetNetworkProfile, 'setNetworkProfileId')
|
||||
declare setNetworkProfile: SetNetworkProfileDto;
|
||||
|
||||
/**
|
||||
* If present, the websocket server that correlates to this configuration slot.
|
||||
* The ws url in the network profile may not match the configured host, for example in the cloud the
|
||||
* configured host will likely be behind a load balancer and a custom DNS name.
|
||||
*
|
||||
*/
|
||||
@ForeignKey(() => ServerNetworkProfile)
|
||||
@Column(DataType.STRING)
|
||||
declare websocketServerConfigId?: string;
|
||||
|
||||
@BelongsTo(() => ServerNetworkProfile, 'websocketServerConfigId')
|
||||
declare websocketServerConfig?: ServerNetworkProfileDto;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: ChargingStationNetworkProfile): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ChargingStationNetworkProfile) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
ConnectorDto,
|
||||
ConnectorErrorCodeEnumType,
|
||||
ConnectorFormatEnumType,
|
||||
ConnectorPowerTypeEnumType,
|
||||
ConnectorStatusEnumType,
|
||||
ConnectorTypeEnumType,
|
||||
EvseDto,
|
||||
EvseTypeDto,
|
||||
MeterValueDto,
|
||||
StartTransactionDto,
|
||||
StatusNotificationDto,
|
||||
TariffDto,
|
||||
TenantDto,
|
||||
TransactionDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP1_6_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { EvseType } from '../DeviceModel/EvseType.js';
|
||||
import { Tariff } from '../Tariff/Tariffs.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { MeterValue } from '../TransactionEvent/MeterValue.js';
|
||||
import { StartTransaction } from '../TransactionEvent/StartTransaction.js';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { Evse } from './Evse.js';
|
||||
import { StatusNotification } from './StatusNotification.js';
|
||||
|
||||
@Table
|
||||
export class Connector extends Model implements ConnectorDto {
|
||||
static readonly MODEL_NAME: string = OCPP1_6_Namespace.Connector;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
unique: 'stationId_connectorId',
|
||||
allowNull: true,
|
||||
type: DataType.INTEGER,
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@Column({
|
||||
allowNull: false,
|
||||
type: DataType.STRING,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@ForeignKey(() => Evse)
|
||||
@Column({
|
||||
unique: 'evseId_evseTypeConnectorId',
|
||||
allowNull: false,
|
||||
type: DataType.INTEGER,
|
||||
})
|
||||
declare evseId: number;
|
||||
|
||||
@Column({
|
||||
unique: 'stationId_connectorId',
|
||||
allowNull: false,
|
||||
type: DataType.INTEGER,
|
||||
})
|
||||
declare connectorId: number; // This is the serial int starting at 1 used in OCPP 1.6 to refer to the connector, unique per Charging Station.
|
||||
|
||||
@ForeignKey(() => EvseType)
|
||||
@Column({
|
||||
unique: 'evseId_evseTypeConnectorId',
|
||||
allowNull: false,
|
||||
type: DataType.INTEGER,
|
||||
})
|
||||
declare evseTypeConnectorId?: number; // This is the serial int starting at 1 used in OCPP 2.0.1 to refer to the connector, unique per EVSE.
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: 'Unknown',
|
||||
})
|
||||
declare status: ConnectorStatusEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare type?: ConnectorTypeEnumType | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare format?: ConnectorFormatEnumType | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: 'NoError',
|
||||
})
|
||||
declare errorCode: ConnectorErrorCodeEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare powerType?: ConnectorPowerTypeEnumType | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare maximumAmperage?: number | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare maximumVoltage?: number | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare maximumPowerWatts?: number | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
return this.getDataValue('timestamp').toISOString();
|
||||
},
|
||||
})
|
||||
declare timestamp: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare info?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare vendorId?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare vendorErrorCode?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare termsAndConditionsUrl?: string | null;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation?: ChargingStationDto;
|
||||
|
||||
@BelongsTo(() => Evse, 'evseId')
|
||||
declare evse?: EvseDto;
|
||||
|
||||
@BelongsTo(() => EvseType, 'evseTypeConnectorId')
|
||||
declare evseTypeByConnector?: EvseTypeDto;
|
||||
|
||||
@HasMany(() => EvseType, 'connectorId')
|
||||
declare evseTypes?: EvseTypeDto[];
|
||||
|
||||
@ForeignKey(() => Tariff)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: true,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
declare tariffId?: number | null;
|
||||
|
||||
@BelongsTo(() => Tariff, 'tariffId')
|
||||
declare tariff?: TariffDto | null;
|
||||
|
||||
@HasMany(() => StatusNotification, 'connectorId')
|
||||
declare statusNotifications?: StatusNotificationDto[];
|
||||
|
||||
@HasMany(() => MeterValue, 'connectorId')
|
||||
declare meterValues?: MeterValueDto[];
|
||||
|
||||
@HasMany(() => Transaction, 'connectorId')
|
||||
declare transactions?: TransactionDto[];
|
||||
|
||||
@HasMany(() => StartTransaction, 'connectorDatabaseId')
|
||||
declare startTransactions?: StartTransactionDto[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: Connector): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Connector) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingNeedsDto,
|
||||
ChargingStationDto,
|
||||
CompositeScheduleDto,
|
||||
ConnectorDto,
|
||||
EvseDto,
|
||||
TenantDto,
|
||||
TransactionDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingNeeds } from '../ChargingProfile/ChargingNeeds.js';
|
||||
import { CompositeSchedule } from '../ChargingProfile/CompositeSchedule.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { Connector } from './Connector.js';
|
||||
|
||||
@Table
|
||||
export class Evse extends Model implements EvseDto {
|
||||
static readonly MODEL_NAME: string = Namespace.Evse;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_evseTypeId',
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_evseTypeId',
|
||||
})
|
||||
declare evseTypeId?: number; // This is the serial int used in OCPP 2.0.1 to refer to the EVSE.
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare evseId: string; // This is the eMI3 compliant EVSE ID
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare physicalReference?: string | null; // Any identifier printed directly on the EVSE
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare removed?: boolean;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation?: ChargingStationDto;
|
||||
|
||||
@HasMany(() => Connector, 'evseId')
|
||||
declare connectors?: ConnectorDto[] | null;
|
||||
|
||||
@HasMany(() => ChargingNeeds, 'evseId')
|
||||
declare chargingNeeds?: ChargingNeedsDto[];
|
||||
|
||||
@HasMany(() => CompositeSchedule, 'evseId')
|
||||
declare compositeSchedules?: CompositeScheduleDto[];
|
||||
|
||||
@HasMany(() => Transaction, 'evseId')
|
||||
declare transactions?: TransactionDto[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: Evse): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Evse) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { StatusNotificationDto, TenantDto } from '@citrineos/base';
|
||||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
OCPP2_Namespace,
|
||||
type ChargingStationDto,
|
||||
type LatestStatusNotificationDto,
|
||||
} from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { StatusNotification } from './StatusNotification.js';
|
||||
|
||||
@Table
|
||||
export class LatestStatusNotification extends Model implements LatestStatusNotificationDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.LatestStatusNotification;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column(DataType.INTEGER)
|
||||
declare stationId?: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation: ChargingStationDto;
|
||||
|
||||
@ForeignKey(() => StatusNotification)
|
||||
declare statusNotificationId: string;
|
||||
|
||||
@BelongsTo(() => StatusNotification, 'statusNotificationId')
|
||||
declare statusNotification: StatusNotificationDto;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: LatestStatusNotification): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: LatestStatusNotification) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
LocationDto,
|
||||
LocationFacilityEnumType,
|
||||
LocationParkingEnumType,
|
||||
Point,
|
||||
TenantDto,
|
||||
TransactionDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, LocationHours, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { Transaction } from '../TransactionEvent/Transaction.js';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
|
||||
/**
|
||||
* Represents a location.
|
||||
* Currently, this data model is internal to CitrineOS. In the future, it will be analogous to an OCPI Location.
|
||||
*/
|
||||
@Table
|
||||
export class Location extends Model implements LocationDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.Location;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare name: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare address: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare city: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare postalCode: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare state: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare country: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: true,
|
||||
})
|
||||
declare publishUpstream: boolean;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
defaultValue: 'UTC',
|
||||
validate: {
|
||||
isTimezone(value: string) {
|
||||
try {
|
||||
Intl.DateTimeFormat(undefined, { timeZone: value });
|
||||
return true;
|
||||
} catch (_ex) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
declare timeZone: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare parkingType?: LocationParkingEnumType | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare facilities?: LocationFacilityEnumType[] | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare openingHours?: LocationHours | null;
|
||||
|
||||
/**
|
||||
* [longitude, latitude]
|
||||
*/
|
||||
@Column(DataType.GEOMETRY('POINT'))
|
||||
declare coordinates: Point;
|
||||
|
||||
@HasMany(() => ChargingStation, 'locationId')
|
||||
declare chargingPool: [ChargingStationDto, ...ChargingStationDto[]];
|
||||
|
||||
@HasMany(() => Transaction, 'locationId')
|
||||
declare transactions?: TransactionDto[];
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Location) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
OCPPVersionType,
|
||||
ServerNetworkProfileDto,
|
||||
TenantDto,
|
||||
WebsocketServerConfig,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { ChargingStationNetworkProfile } from './ChargingStationNetworkProfile.js';
|
||||
|
||||
@Table
|
||||
export class ServerNetworkProfile
|
||||
extends Model
|
||||
implements WebsocketServerConfig, ServerNetworkProfileDto
|
||||
{
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.ServerNetworkProfile;
|
||||
|
||||
@PrimaryKey
|
||||
@Column(DataType.STRING)
|
||||
declare id: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare host: string;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare port: number;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare pingInterval: number;
|
||||
|
||||
@Column(DataType.ARRAY(DataType.STRING))
|
||||
declare protocols: OCPPVersionType[];
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare messageTimeout: number;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare securityProfile: number;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare allowUnknownChargingStations: boolean;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare dynamicTenantResolution: boolean;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare tenantPathMapping?: Record<string, number>;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare tlsKeyFilePath?: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare tlsCertificateChainFilePath?: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare mtlsCertificateAuthorityKeyFilePath?: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare rootCACertificateFilePath?: string;
|
||||
|
||||
@BelongsToMany(() => ChargingStation, () => ChargingStationNetworkProfile)
|
||||
declare chargingStations?: ChargingStationDto[] | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: true,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId?: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: ServerNetworkProfile) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
type OCPPInterfaceEnumType,
|
||||
type OCPPTransportEnumType,
|
||||
type SetNetworkProfileDto,
|
||||
} from '@citrineos/base';
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
OCPPVersionEnumType,
|
||||
ServerNetworkProfileDto,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { ServerNetworkProfile } from './ServerNetworkProfile.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
/**
|
||||
* The CallMessage model can be extended with new optional fields,
|
||||
* e.g. chargingProfileId, for other correlationId related lookups.
|
||||
*/
|
||||
@Table
|
||||
export class SetNetworkProfile extends Model implements SetNetworkProfileDto {
|
||||
static readonly MODEL_NAME: string = 'SetNetworkProfile';
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationId_correlationId',
|
||||
})
|
||||
declare stationId?: number;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation?: ChargingStationDto;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Index
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationId_correlationId',
|
||||
})
|
||||
declare correlationId: string;
|
||||
|
||||
@ForeignKey(() => ServerNetworkProfile)
|
||||
@Column(DataType.STRING)
|
||||
declare websocketServerConfigId?: string;
|
||||
|
||||
@BelongsTo(() => ServerNetworkProfile, 'websocketServerConfigId')
|
||||
declare websocketServerConfig?: ServerNetworkProfileDto;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare configurationSlot: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppVersion: OCPPVersionEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppTransport: OCPPTransportEnumType;
|
||||
|
||||
/**
|
||||
* Communication_ Function. OCPP_ Central_ System_ URL. URI
|
||||
* urn:x-oca:ocpp:uid:1:569357
|
||||
* URL of the CSMS(s) that this Charging Station communicates with.
|
||||
*
|
||||
*/
|
||||
@Column(DataType.STRING)
|
||||
declare ocppCsmsUrl: string;
|
||||
|
||||
/**
|
||||
* Duration in seconds before a message send by the Charging Station via this network connection times-out.
|
||||
* The best setting depends on the underlying network and response times of the CSMS.
|
||||
* If you are looking for a some guideline: use 30 seconds as a starting point.
|
||||
*
|
||||
*/
|
||||
@Column(DataType.INTEGER)
|
||||
declare messageTimeout: number;
|
||||
|
||||
/**
|
||||
* This field specifies the security profile used when connecting to the CSMS with this NetworkConnectionProfile.
|
||||
*
|
||||
*/
|
||||
@Column(DataType.INTEGER)
|
||||
declare securityProfile: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppInterface: OCPPInterfaceEnumType;
|
||||
|
||||
/**
|
||||
* Stringified JSON of {@link OCPP2_0_1.APNType} for display purposes only
|
||||
*
|
||||
*/
|
||||
@Column(DataType.STRING)
|
||||
declare apn?: string;
|
||||
|
||||
/**
|
||||
* Stringified JSON of {@link OCPP2_0_1.VPNType} for display purposes only
|
||||
*
|
||||
*/
|
||||
@Column(DataType.STRING)
|
||||
declare vpn?: string;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: SetNetworkProfile): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: SetNetworkProfile) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ChargingStationDto,
|
||||
ConnectorStatusEnumType,
|
||||
ConnectorDto,
|
||||
StatusNotificationDto,
|
||||
TenantDto,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from './ChargingStation.js';
|
||||
import { Connector } from './Connector.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class StatusNotification extends Model implements StatusNotificationDto {
|
||||
static readonly MODEL_NAME: string = Namespace.StatusNotificationRequest;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column(DataType.INTEGER)
|
||||
declare stationId?: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation: ChargingStationDto;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const timestamp = this.getDataValue('timestamp');
|
||||
return timestamp ? timestamp.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare timestamp?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare connectorStatus: ConnectorStatusEnumType;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare evseId?: number | null;
|
||||
|
||||
@Column(DataType.INTEGER)
|
||||
declare connectorId: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare errorCode?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare info?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare vendorId?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare vendorErrorCode?: string | null;
|
||||
|
||||
declare customData?: object | null;
|
||||
|
||||
@BelongsTo(() => Connector, 'connectorId')
|
||||
declare connector?: ConnectorDto;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: StatusNotification): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: StatusNotification) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { Location } from './Location.js';
|
||||
export { ChargingStation } from './ChargingStation.js';
|
||||
export { Evse } from './Evse.js';
|
||||
export { ChargingStationNetworkProfile } from './ChargingStationNetworkProfile.js';
|
||||
export { LatestStatusNotification } from './LatestStatusNotification.js';
|
||||
export { StatusNotification } from './StatusNotification.js';
|
||||
export { ServerNetworkProfile } from './ServerNetworkProfile.js';
|
||||
export { SetNetworkProfile } from './SetNetworkProfile.js';
|
||||
export { Connector } from './Connector.js';
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type {
|
||||
ComponentDto,
|
||||
MessageInfoDto,
|
||||
MessagePriorityEnumType,
|
||||
MessageStateEnumType,
|
||||
TenantDto,
|
||||
MessageContent,
|
||||
} from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Component } from '../DeviceModel/Component.js';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class MessageInfo extends Model implements MessageInfoDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.MessageInfoType;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column(DataType.INTEGER)
|
||||
declare databaseId: number;
|
||||
|
||||
@Index
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
unique: 'stationName_tenantId_id',
|
||||
type: DataType.INTEGER,
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare priority: MessagePriorityEnumType;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare state?: MessageStateEnumType | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const startDateTime: Date = this.getDataValue('startDateTime');
|
||||
return startDateTime ? startDateTime.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare startDateTime?: string | null;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const endDateTime: Date = this.getDataValue('endDateTime');
|
||||
return endDateTime ? endDateTime.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare endDateTime?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare transactionId?: string | null;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
declare message: MessageContent;
|
||||
|
||||
@Column(DataType.BOOLEAN)
|
||||
declare active: boolean;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
@BelongsTo(() => Component, 'displayComponentId')
|
||||
declare display: ComponentDto;
|
||||
|
||||
@ForeignKey(() => Component)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
})
|
||||
declare displayComponentId?: number | null;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: MessageInfo) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
export { MessageInfo } from './MessageInfo.js';
|
||||
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChargingStationDto, MessageState, OCPPMessageDto, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, MessageOrigin, Namespace, OCPPVersion } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
HasMany,
|
||||
Index,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { ChargingStation } from './Location/index.js';
|
||||
import { Tenant } from './Tenant.js';
|
||||
|
||||
@Table
|
||||
export class OCPPMessage extends Model implements OCPPMessageDto {
|
||||
static readonly MODEL_NAME: string = Namespace.OCPPMessage;
|
||||
|
||||
@ForeignKey(() => ChargingStation)
|
||||
@Column(DataType.INTEGER)
|
||||
declare stationId?: number;
|
||||
|
||||
@Index
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Index
|
||||
@Column(DataType.STRING)
|
||||
declare correlationId?: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare origin: MessageOrigin;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare state: MessageState;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare protocol: OCPPVersion;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare action: string;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare message: any;
|
||||
|
||||
@BelongsTo(() => ChargingStation, 'stationId')
|
||||
declare chargingStation?: ChargingStationDto;
|
||||
|
||||
@ForeignKey(() => OCPPMessage)
|
||||
@Index
|
||||
@Column(DataType.INTEGER)
|
||||
declare requestMessageId?: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
return this.getDataValue('timestamp')?.toISOString();
|
||||
},
|
||||
})
|
||||
declare timestamp: string;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BelongsTo(() => OCPPMessage, { foreignKey: 'requestMessageId', as: 'requestMessage' })
|
||||
declare requestMessage?: OCPPMessage;
|
||||
|
||||
@HasMany(() => OCPPMessage, { foreignKey: 'requestMessageId', as: 'responseMessages' })
|
||||
declare responseMessages?: OCPPMessage[];
|
||||
|
||||
@BeforeCreate
|
||||
static async resolveStationId(instance: OCPPMessage): Promise<void> {
|
||||
if (instance.stationId == null && instance.ocppConnectionName && instance.tenantId != null) {
|
||||
const station = await ChargingStation.findOne({
|
||||
where: { ocppConnectionName: instance.ocppConnectionName, tenantId: instance.tenantId },
|
||||
attributes: ['id'],
|
||||
});
|
||||
if (station) {
|
||||
instance.stationId = station.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: OCPPMessage) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { EvseTypeDto, ReservationDto, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, Namespace } from '@citrineos/base';
|
||||
import {
|
||||
AutoIncrement,
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Model,
|
||||
PrimaryKey,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { EvseType } from './DeviceModel/index.js';
|
||||
import { Tenant } from './Tenant.js';
|
||||
|
||||
@Table
|
||||
export class Reservation extends Model implements ReservationDto {
|
||||
static readonly MODEL_NAME: string = Namespace.ReserveNowRequest;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column(DataType.INTEGER)
|
||||
declare databaseId: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
const expiryDateTime: Date = this.getDataValue('expiryDateTime');
|
||||
return expiryDateTime ? expiryDateTime.toISOString() : null;
|
||||
},
|
||||
})
|
||||
declare expiryDateTime: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare connectorType?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare reserveStatus?: string | null;
|
||||
|
||||
@Column({ type: DataType.BOOLEAN, defaultValue: false })
|
||||
declare isActive: boolean;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare terminatedByTransaction?: string | null;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare idToken: object;
|
||||
|
||||
@Column(DataType.JSONB)
|
||||
declare groupIdToken?: object | null;
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
@ForeignKey(() => EvseType)
|
||||
declare evseId?: number | null;
|
||||
|
||||
@BelongsTo(() => EvseType, 'evseId')
|
||||
declare evse?: EvseTypeDto | null;
|
||||
|
||||
declare customData?: any | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
unique: 'stationName_tenantId_id',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Reservation) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import { DEFAULT_TENANT_ID, OCPP2_0_1, OCPP2_Namespace } from '@citrineos/base';
|
||||
import type { SecurityEventDto, TenantDto } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from './Tenant.js';
|
||||
|
||||
@Table
|
||||
export class SecurityEvent extends Model implements SecurityEventDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.SecurityEventNotificationRequest;
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
@Index
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare type: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
get() {
|
||||
return this.getDataValue('timestamp').toISOString();
|
||||
},
|
||||
})
|
||||
declare timestamp: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare techInfo?: string | null;
|
||||
|
||||
declare customData?: OCPP2_0_1.CustomDataType | null;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: SecurityEvent) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
import type { SubscriptionDto, TenantDto } from '@citrineos/base';
|
||||
import { DEFAULT_TENANT_ID, OCPP2_Namespace } from '@citrineos/base';
|
||||
import {
|
||||
BeforeCreate,
|
||||
BeforeUpdate,
|
||||
BelongsTo,
|
||||
Column,
|
||||
DataType,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Model,
|
||||
Table,
|
||||
} from 'sequelize-typescript';
|
||||
import { Tenant } from '../Tenant.js';
|
||||
|
||||
@Table
|
||||
export class Subscription extends Model implements SubscriptionDto {
|
||||
static readonly MODEL_NAME: string = OCPP2_Namespace.Subscription;
|
||||
|
||||
@Index
|
||||
@Column(DataType.STRING)
|
||||
declare ocppConnectionName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare onConnect: boolean;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare onClose: boolean;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare onMessage: boolean;
|
||||
|
||||
@Column({
|
||||
type: DataType.BOOLEAN,
|
||||
defaultValue: false,
|
||||
})
|
||||
declare sentMessage: boolean;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare messageRegexFilter?: string | null;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
declare url: string;
|
||||
|
||||
@ForeignKey(() => Tenant)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
declare tenantId: number;
|
||||
|
||||
@BelongsTo(() => Tenant, 'tenantId')
|
||||
declare tenant?: TenantDto;
|
||||
|
||||
@BeforeUpdate
|
||||
@BeforeCreate
|
||||
static setDefaultTenant(instance: Subscription) {
|
||||
if (instance.tenantId == null) {
|
||||
instance.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
if (this.tenantId == null) {
|
||||
this.tenantId = DEFAULT_TENANT_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user