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