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