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