Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
953d6b02 JB |
2 | |
3 | import type { JSONSchemaType } from 'ajv'; | |
4 | ||
78202038 | 5 | import { OCPP20ServiceUtils } from './OCPP20ServiceUtils'; |
268a74bb | 6 | import { OCPPError } from '../../../exception'; |
b3fc3ff5 | 7 | import { |
268a74bb JB |
8 | ErrorType, |
9 | type JsonObject, | |
10 | type JsonType, | |
268a74bb JB |
11 | type OCPP20BootNotificationResponse, |
12 | type OCPP20ClearCacheResponse, | |
13 | type OCPP20HeartbeatResponse, | |
b3fc3ff5 | 14 | OCPP20IncomingRequestCommand, |
857d8dd9 | 15 | OCPP20OptionalVariableName, |
b3fc3ff5 | 16 | OCPP20RequestCommand, |
268a74bb JB |
17 | type OCPP20StatusNotificationResponse, |
18 | OCPPVersion, | |
19 | RegistrationStatusEnumType, | |
20 | type ResponseHandler, | |
21 | } from '../../../types'; | |
22 | import { logger } from '../../../utils/Logger'; | |
23 | import type { ChargingStation } from '../../ChargingStation'; | |
24 | import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils'; | |
25 | import { OCPPResponseService } from '../OCPPResponseService'; | |
953d6b02 JB |
26 | |
27 | const moduleName = 'OCPP20ResponseService'; | |
28 | ||
268a74bb | 29 | export class OCPP20ResponseService extends OCPPResponseService { |
b3fc3ff5 JB |
30 | public jsonIncomingRequestResponseSchemas: Map< |
31 | OCPP20IncomingRequestCommand, | |
32 | JSONSchemaType<JsonObject> | |
33 | >; | |
34 | ||
953d6b02 JB |
35 | private responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>; |
36 | private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>; | |
37 | ||
38 | public constructor() { | |
39 | if (new.target?.name === moduleName) { | |
40 | throw new TypeError(`Cannot construct ${new.target?.name} instances directly`); | |
41 | } | |
d270cc87 JB |
42 | super(OCPPVersion.VERSION_20); |
43 | this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([ | |
44 | [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)], | |
81533a20 | 45 | [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)], |
6e939d9e | 46 | [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)], |
d270cc87 JB |
47 | ]); |
48 | this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([ | |
49 | [ | |
50 | OCPP20RequestCommand.BOOT_NOTIFICATION, | |
130783a7 | 51 | OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>( |
1b271a54 JB |
52 | '../../../assets/json-schemas/ocpp/2.0/BootNotificationResponse.json', |
53 | moduleName, | |
54 | 'constructor' | |
e9a4164c | 55 | ), |
d270cc87 | 56 | ], |
81533a20 JB |
57 | [ |
58 | OCPP20RequestCommand.HEARTBEAT, | |
130783a7 | 59 | OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>( |
1b271a54 JB |
60 | '../../../assets/json-schemas/ocpp/2.0/HeartbeatResponse.json', |
61 | moduleName, | |
62 | 'constructor' | |
e9a4164c | 63 | ), |
81533a20 | 64 | ], |
6e939d9e JB |
65 | [ |
66 | OCPP20RequestCommand.STATUS_NOTIFICATION, | |
130783a7 | 67 | OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>( |
1b271a54 JB |
68 | '../../../assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json', |
69 | moduleName, | |
70 | 'constructor' | |
e9a4164c | 71 | ), |
6e939d9e | 72 | ], |
d270cc87 | 73 | ]); |
02887891 JB |
74 | this.jsonIncomingRequestResponseSchemas = new Map([ |
75 | [ | |
76 | OCPP20IncomingRequestCommand.CLEAR_CACHE, | |
130783a7 | 77 | OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>( |
1b271a54 JB |
78 | '../../../assets/json-schemas/ocpp/2.0/ClearCacheResponse.json', |
79 | moduleName, | |
80 | 'constructor' | |
e9a4164c | 81 | ), |
02887891 JB |
82 | ], |
83 | ]); | |
953d6b02 JB |
84 | this.validatePayload.bind(this); |
85 | } | |
86 | ||
87 | public async responseHandler( | |
88 | chargingStation: ChargingStation, | |
89 | commandName: OCPP20RequestCommand, | |
90 | payload: JsonType, | |
91 | requestPayload: JsonType | |
92 | ): Promise<void> { | |
93 | if ( | |
d270cc87 JB |
94 | chargingStation.isRegistered() === true || |
95 | commandName === OCPP20RequestCommand.BOOT_NOTIFICATION | |
953d6b02 JB |
96 | ) { |
97 | if ( | |
98 | this.responseHandlers.has(commandName) === true && | |
99 | OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true | |
100 | ) { | |
101 | try { | |
102 | this.validatePayload(chargingStation, commandName, payload); | |
103 | await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload); | |
104 | } catch (error) { | |
105 | logger.error( | |
106 | `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`, | |
107 | error | |
108 | ); | |
109 | throw error; | |
110 | } | |
111 | } else { | |
112 | // Throw exception | |
113 | throw new OCPPError( | |
114 | ErrorType.NOT_IMPLEMENTED, | |
115 | `${commandName} is not implemented to handle response PDU ${JSON.stringify( | |
116 | payload, | |
117 | null, | |
118 | 2 | |
119 | )}`, | |
120 | commandName, | |
121 | payload | |
122 | ); | |
123 | } | |
124 | } else { | |
125 | throw new OCPPError( | |
126 | ErrorType.SECURITY_ERROR, | |
127 | `${commandName} cannot be issued to handle response PDU ${JSON.stringify( | |
128 | payload, | |
129 | null, | |
130 | 2 | |
439fc71b | 131 | )} while the charging station is not registered on the central server.`, |
953d6b02 JB |
132 | commandName, |
133 | payload | |
134 | ); | |
135 | } | |
136 | } | |
137 | ||
138 | private validatePayload( | |
139 | chargingStation: ChargingStation, | |
140 | commandName: OCPP20RequestCommand, | |
141 | payload: JsonType | |
142 | ): boolean { | |
45988780 | 143 | if (this.jsonSchemas.has(commandName) === true) { |
953d6b02 JB |
144 | return this.validateResponsePayload( |
145 | chargingStation, | |
146 | commandName, | |
147 | this.jsonSchemas.get(commandName), | |
148 | payload | |
149 | ); | |
150 | } | |
151 | logger.warn( | |
b3fc3ff5 | 152 | `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation` |
953d6b02 JB |
153 | ); |
154 | return false; | |
155 | } | |
d270cc87 JB |
156 | |
157 | private handleResponseBootNotification( | |
158 | chargingStation: ChargingStation, | |
159 | payload: OCPP20BootNotificationResponse | |
160 | ): void { | |
161 | if (payload.status === RegistrationStatusEnumType.ACCEPTED) { | |
268a74bb JB |
162 | ChargingStationConfigurationUtils.addConfigurationKey( |
163 | chargingStation, | |
857d8dd9 | 164 | OCPP20OptionalVariableName.HeartbeatInterval, |
268a74bb JB |
165 | payload.interval.toString(), |
166 | {}, | |
167 | { overwrite: true, save: true } | |
168 | ); | |
d270cc87 JB |
169 | chargingStation.heartbeatSetInterval |
170 | ? chargingStation.restartHeartbeat() | |
171 | : chargingStation.startHeartbeat(); | |
172 | } | |
173 | if (Object.values(RegistrationStatusEnumType).includes(payload.status)) { | |
174 | const logMsg = `${chargingStation.logPrefix()} Charging station in '${ | |
175 | payload.status | |
176 | }' state on the central server`; | |
177 | payload.status === RegistrationStatusEnumType.REJECTED | |
178 | ? logger.warn(logMsg) | |
179 | : logger.info(logMsg); | |
180 | } else { | |
181 | logger.error( | |
44eb6026 | 182 | `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`, |
d270cc87 JB |
183 | payload |
184 | ); | |
185 | } | |
186 | } | |
953d6b02 | 187 | } |