OCPP: Cleanup helpers
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
1 import type { DefinedError, ErrorObject } from 'ajv';
2
3 import BaseError from '../../exception/BaseError';
4 import type { SampledValueTemplate } from '../../types/MeasurandPerPhaseSampledValueTemplates';
5 import { StandardParametersKey } from '../../types/ocpp/Configuration';
6 import { ErrorType } from '../../types/ocpp/ErrorType';
7 import { MeterValueMeasurand, type MeterValuePhase } from '../../types/ocpp/MeterValues';
8 import { IncomingRequestCommand, RequestCommand } from '../../types/ocpp/Requests';
9 import Constants from '../../utils/Constants';
10 import logger from '../../utils/Logger';
11 import Utils from '../../utils/Utils';
12 import type ChargingStation from '../ChargingStation';
13 import { ChargingStationConfigurationUtils } from '../ChargingStationConfigurationUtils';
14
15 export class OCPPServiceUtils {
16 protected constructor() {
17 // This is intentional
18 }
19
20 public static ajvErrorsToErrorType(errors: ErrorObject[]): ErrorType {
21 for (const error of errors as DefinedError[]) {
22 switch (error.keyword) {
23 case 'type':
24 return ErrorType.TYPE_CONSTRAINT_VIOLATION;
25 case 'dependencies':
26 case 'required':
27 return ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION;
28 case 'pattern':
29 case 'format':
30 return ErrorType.PROPERTY_CONSTRAINT_VIOLATION;
31 }
32 }
33 return ErrorType.FORMAT_VIOLATION;
34 }
35
36 public static isRequestCommandSupported(
37 chargingStation: ChargingStation,
38 command: RequestCommand
39 ): boolean {
40 const isRequestCommand = Object.values(RequestCommand).includes(command);
41 if (
42 isRequestCommand === true &&
43 !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
44 ) {
45 return true;
46 } else if (
47 isRequestCommand === true &&
48 chargingStation.stationInfo?.commandsSupport?.outgoingCommands
49 ) {
50 return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
51 }
52 logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
53 return false;
54 }
55
56 public static isIncomingRequestCommandSupported(
57 chargingStation: ChargingStation,
58 command: IncomingRequestCommand
59 ): boolean {
60 const isIncomingRequestCommand = Object.values(IncomingRequestCommand).includes(command);
61 if (
62 isIncomingRequestCommand === true &&
63 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
64 ) {
65 return true;
66 } else if (
67 isIncomingRequestCommand === true &&
68 chargingStation.stationInfo?.commandsSupport?.incomingCommands
69 ) {
70 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
71 }
72 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
73 return false;
74 }
75
76 protected static getSampledValueTemplate(
77 chargingStation: ChargingStation,
78 connectorId: number,
79 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
80 phase?: MeterValuePhase
81 ): SampledValueTemplate | undefined {
82 const onPhaseStr = phase ? `on phase ${phase} ` : '';
83 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
84 logger.warn(
85 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
86 );
87 return;
88 }
89 if (
90 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
91 !ChargingStationConfigurationUtils.getConfigurationKey(
92 chargingStation,
93 StandardParametersKey.MeterValuesSampledData
94 )?.value.includes(measurand)
95 ) {
96 logger.debug(
97 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
98 StandardParametersKey.MeterValuesSampledData
99 }' OCPP parameter`
100 );
101 return;
102 }
103 const sampledValueTemplates: SampledValueTemplate[] =
104 chargingStation.getConnectorStatus(connectorId).MeterValues;
105 for (
106 let index = 0;
107 !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length;
108 index++
109 ) {
110 if (
111 Constants.SUPPORTED_MEASURANDS.includes(
112 sampledValueTemplates[index]?.measurand ??
113 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
114 ) === false
115 ) {
116 logger.warn(
117 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
118 );
119 } else if (
120 phase &&
121 sampledValueTemplates[index]?.phase === phase &&
122 sampledValueTemplates[index]?.measurand === measurand &&
123 ChargingStationConfigurationUtils.getConfigurationKey(
124 chargingStation,
125 StandardParametersKey.MeterValuesSampledData
126 )?.value.includes(measurand) === true
127 ) {
128 return sampledValueTemplates[index];
129 } else if (
130 !phase &&
131 !sampledValueTemplates[index].phase &&
132 sampledValueTemplates[index]?.measurand === measurand &&
133 ChargingStationConfigurationUtils.getConfigurationKey(
134 chargingStation,
135 StandardParametersKey.MeterValuesSampledData
136 )?.value.includes(measurand) === true
137 ) {
138 return sampledValueTemplates[index];
139 } else if (
140 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
141 (!sampledValueTemplates[index].measurand ||
142 sampledValueTemplates[index].measurand === measurand)
143 ) {
144 return sampledValueTemplates[index];
145 }
146 }
147 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
148 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
149 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
150 throw new BaseError(errorMsg);
151 }
152 logger.debug(
153 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
154 );
155 }
156
157 protected static getLimitFromSampledValueTemplateCustomValue(
158 value: string,
159 limit: number,
160 options: { limitationEnabled?: boolean; unitMultiplier?: number } = {
161 limitationEnabled: true,
162 unitMultiplier: 1,
163 }
164 ): number {
165 options.limitationEnabled = options?.limitationEnabled ?? true;
166 options.unitMultiplier = options?.unitMultiplier ?? 1;
167 const numberValue = isNaN(parseInt(value)) ? Infinity : parseInt(value);
168 return options?.limitationEnabled
169 ? Math.min(numberValue * options.unitMultiplier, limit)
170 : numberValue * options.unitMultiplier;
171 }
172 }