feat(ui): use toggle button to star/stop simulator
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
2
3 import { createWriteStream, readdirSync } from 'node:fs'
4 import { dirname, join, resolve } from 'node:path'
5 import { URL, fileURLToPath } from 'node:url'
6
7 import type { ValidateFunction } from 'ajv'
8 import { Client, type FTPResponse } from 'basic-ftp'
9 import {
10 type Interval,
11 addSeconds,
12 differenceInSeconds,
13 isDate,
14 secondsToMilliseconds
15 } from 'date-fns'
16 import { maxTime } from 'date-fns/constants'
17 import { create } from 'tar'
18
19 import { OCPP16Constants } from './OCPP16Constants.js'
20 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
21 import {
22 type ChargingStation,
23 canProceedChargingProfile,
24 checkChargingStation,
25 getConfigurationKey,
26 getConnectorChargingProfiles,
27 prepareChargingProfileKind,
28 removeExpiredReservations,
29 setConfigurationKeyValue
30 } from '../../../charging-station/index.js'
31 import { OCPPError } from '../../../exception/index.js'
32 import {
33 type ChangeConfigurationRequest,
34 type ChangeConfigurationResponse,
35 ErrorType,
36 type GenericResponse,
37 GenericStatus,
38 type GetConfigurationRequest,
39 type GetConfigurationResponse,
40 type GetDiagnosticsRequest,
41 type GetDiagnosticsResponse,
42 type IncomingRequestHandler,
43 type JsonType,
44 OCPP16AuthorizationStatus,
45 OCPP16AvailabilityType,
46 type OCPP16BootNotificationRequest,
47 type OCPP16BootNotificationResponse,
48 type OCPP16CancelReservationRequest,
49 type OCPP16ChangeAvailabilityRequest,
50 type OCPP16ChangeAvailabilityResponse,
51 OCPP16ChargePointErrorCode,
52 OCPP16ChargePointStatus,
53 type OCPP16ChargingProfile,
54 OCPP16ChargingProfilePurposeType,
55 type OCPP16ChargingSchedule,
56 type OCPP16ClearCacheRequest,
57 type OCPP16ClearChargingProfileRequest,
58 type OCPP16ClearChargingProfileResponse,
59 type OCPP16DataTransferRequest,
60 type OCPP16DataTransferResponse,
61 OCPP16DataTransferVendorId,
62 OCPP16DiagnosticsStatus,
63 type OCPP16DiagnosticsStatusNotificationRequest,
64 type OCPP16DiagnosticsStatusNotificationResponse,
65 OCPP16FirmwareStatus,
66 type OCPP16FirmwareStatusNotificationRequest,
67 type OCPP16FirmwareStatusNotificationResponse,
68 type OCPP16GetCompositeScheduleRequest,
69 type OCPP16GetCompositeScheduleResponse,
70 type OCPP16HeartbeatRequest,
71 type OCPP16HeartbeatResponse,
72 OCPP16IncomingRequestCommand,
73 OCPP16MessageTrigger,
74 OCPP16RequestCommand,
75 type OCPP16ReserveNowRequest,
76 type OCPP16ReserveNowResponse,
77 OCPP16StandardParametersKey,
78 type OCPP16StartTransactionRequest,
79 type OCPP16StartTransactionResponse,
80 type OCPP16StatusNotificationRequest,
81 type OCPP16StatusNotificationResponse,
82 OCPP16StopTransactionReason,
83 OCPP16SupportedFeatureProfiles,
84 type OCPP16TriggerMessageRequest,
85 type OCPP16TriggerMessageResponse,
86 OCPP16TriggerMessageStatus,
87 type OCPP16UpdateFirmwareRequest,
88 type OCPP16UpdateFirmwareResponse,
89 type OCPPConfigurationKey,
90 OCPPVersion,
91 type RemoteStartTransactionRequest,
92 type RemoteStopTransactionRequest,
93 ReservationTerminationReason,
94 type ResetRequest,
95 type SetChargingProfileRequest,
96 type SetChargingProfileResponse,
97 type UnlockConnectorRequest,
98 type UnlockConnectorResponse
99 } from '../../../types/index.js'
100 import {
101 Constants,
102 convertToDate,
103 convertToInt,
104 formatDurationMilliSeconds,
105 getRandomInteger,
106 isAsyncFunction,
107 isEmptyArray,
108 isNotEmptyArray,
109 isNotEmptyString,
110 logger,
111 sleep
112 } from '../../../utils/index.js'
113 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
114
115 const moduleName = 'OCPP16IncomingRequestService'
116
117 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
118 protected payloadValidateFunctions: Map<OCPP16IncomingRequestCommand, ValidateFunction<JsonType>>
119
120 private readonly incomingRequestHandlers: Map<
121 OCPP16IncomingRequestCommand,
122 IncomingRequestHandler
123 >
124
125 public constructor () {
126 // if (new.target.name === moduleName) {
127 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
128 // }
129 super(OCPPVersion.VERSION_16)
130 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
131 [
132 OCPP16IncomingRequestCommand.RESET,
133 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler
134 ],
135 [
136 OCPP16IncomingRequestCommand.CLEAR_CACHE,
137 this.handleRequestClearCache.bind(this) as IncomingRequestHandler
138 ],
139 [
140 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
141 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler
142 ],
143 [
144 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
145 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler
146 ],
147 [
148 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
149 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler
150 ],
151 [
152 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
153 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler
154 ],
155 [
156 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
157 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler
158 ],
159 [
160 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
161 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler
162 ],
163 [
164 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
165 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler
166 ],
167 [
168 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
169 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler
170 ],
171 [
172 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
173 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler
174 ],
175 [
176 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
177 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler
178 ],
179 [
180 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
181 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler
182 ],
183 [
184 OCPP16IncomingRequestCommand.DATA_TRANSFER,
185 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler
186 ],
187 [
188 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
189 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler
190 ],
191 [
192 OCPP16IncomingRequestCommand.RESERVE_NOW,
193 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler
194 ],
195 [
196 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
197 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler
198 ]
199 ])
200 this.payloadValidateFunctions = new Map<
201 OCPP16IncomingRequestCommand,
202 ValidateFunction<JsonType>
203 >([
204 [
205 OCPP16IncomingRequestCommand.RESET,
206 this.ajv
207 .compile(
208 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
209 'assets/json-schemas/ocpp/1.6/Reset.json',
210 moduleName,
211 'constructor'
212 )
213 )
214 .bind(this)
215 ],
216 [
217 OCPP16IncomingRequestCommand.CLEAR_CACHE,
218 this.ajv
219 .compile(
220 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
221 'assets/json-schemas/ocpp/1.6/ClearCache.json',
222 moduleName,
223 'constructor'
224 )
225 )
226 .bind(this)
227 ],
228 [
229 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
230 this.ajv
231 .compile(
232 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
233 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
234 moduleName,
235 'constructor'
236 )
237 )
238 .bind(this)
239 ],
240 [
241 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
242 this.ajv
243 .compile(
244 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
245 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
246 moduleName,
247 'constructor'
248 )
249 )
250 .bind(this)
251 ],
252 [
253 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
254 this.ajv
255 .compile(
256 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
257 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
258 moduleName,
259 'constructor'
260 )
261 )
262 .bind(this)
263 ],
264 [
265 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
266 this.ajv
267 .compile(
268 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
269 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
270 moduleName,
271 'constructor'
272 )
273 )
274 .bind(this)
275 ],
276 [
277 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
278 this.ajv
279 .compile(
280 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
281 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
282 moduleName,
283 'constructor'
284 )
285 )
286 .bind(this)
287 ],
288 [
289 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
290 this.ajv
291 .compile(
292 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
293 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
294 moduleName,
295 'constructor'
296 )
297 )
298 .bind(this)
299 ],
300 [
301 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
302 this.ajv
303 .compile(
304 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
305 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
306 moduleName,
307 'constructor'
308 )
309 )
310 .bind(this)
311 ],
312 [
313 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
314 this.ajv
315 .compile(
316 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
317 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
318 moduleName,
319 'constructor'
320 )
321 )
322 .bind(this)
323 ],
324 [
325 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
326 this.ajv
327 .compile(
328 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
329 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
330 moduleName,
331 'constructor'
332 )
333 )
334 .bind(this)
335 ],
336 [
337 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
338 this.ajv
339 .compile(
340 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
341 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
342 moduleName,
343 'constructor'
344 )
345 )
346 .bind(this)
347 ],
348 [
349 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
350 this.ajv
351 .compile(
352 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
353 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
354 moduleName,
355 'constructor'
356 )
357 )
358 .bind(this)
359 ],
360 [
361 OCPP16IncomingRequestCommand.DATA_TRANSFER,
362 this.ajv
363 .compile(
364 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
365 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
366 moduleName,
367 'constructor'
368 )
369 )
370 .bind(this)
371 ],
372 [
373 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
374 this.ajv
375 .compile(
376 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
377 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
378 moduleName,
379 'constructor'
380 )
381 )
382 .bind(this)
383 ],
384 [
385 OCPP16IncomingRequestCommand.RESERVE_NOW,
386 this.ajv
387 .compile(
388 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
389 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
390 moduleName,
391 'constructor'
392 )
393 )
394 .bind(this)
395 ],
396 [
397 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
398 this.ajv
399 .compile(
400 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
401 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
402 moduleName,
403 'constructor'
404 )
405 )
406 .bind(this)
407 ]
408 ])
409 // Handle incoming request events
410 this.on(
411 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
412 (
413 chargingStation: ChargingStation,
414 request: RemoteStartTransactionRequest,
415 response: GenericResponse
416 ) => {
417 if (response.status === GenericStatus.Accepted) {
418 const { connectorId, idTag } = request
419 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
420 chargingStation.getConnectorStatus(connectorId)!.transactionRemoteStarted = true
421 chargingStation.ocppRequestService
422 .requestHandler<OCPP16StartTransactionRequest, OCPP16StartTransactionResponse>(
423 chargingStation,
424 OCPP16RequestCommand.START_TRANSACTION,
425 {
426 connectorId,
427 idTag
428 }
429 )
430 .then(response => {
431 if (response.status === OCPP16AuthorizationStatus.ACCEPTED) {
432 logger.debug(
433 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
434 )
435 } else {
436 logger.debug(
437 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${idTag}'`
438 )
439 }
440 })
441 .catch(error => {
442 logger.error(
443 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
444 error
445 )
446 })
447 }
448 }
449 )
450 this.on(
451 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
452 (
453 chargingStation: ChargingStation,
454 request: RemoteStopTransactionRequest,
455 response: GenericResponse
456 ) => {
457 if (response.status === GenericStatus.Accepted) {
458 const { transactionId } = request
459 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
460 const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)!
461 OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
462 .then(response => {
463 if (response.status === GenericStatus.Accepted) {
464 logger.debug(
465 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for transaction '${transactionId}'`
466 )
467 } else {
468 logger.debug(
469 `${chargingStation.logPrefix()} Remote stop transaction REJECTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for transaction '${transactionId}'`
470 )
471 }
472 })
473 .catch(error => {
474 logger.error(
475 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote stop transaction error:`,
476 error
477 )
478 })
479 }
480 }
481 )
482 this.on(
483 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
484 (
485 chargingStation: ChargingStation,
486 request: OCPP16TriggerMessageRequest,
487 response: OCPP16TriggerMessageResponse
488 ) => {
489 if (response.status !== OCPP16TriggerMessageStatus.ACCEPTED) {
490 return
491 }
492 const { requestedMessage, connectorId } = request
493 const errorHandler = (error: Error): void => {
494 logger.error(
495 `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
496 error
497 )
498 }
499 switch (requestedMessage) {
500 case OCPP16MessageTrigger.BootNotification:
501 chargingStation.ocppRequestService
502 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
503 chargingStation,
504 OCPP16RequestCommand.BOOT_NOTIFICATION,
505 chargingStation.bootNotificationRequest,
506 { skipBufferingOnError: true, triggerMessage: true }
507 )
508 .then(response => {
509 chargingStation.bootNotificationResponse = response
510 })
511 .catch(errorHandler)
512 break
513 case OCPP16MessageTrigger.Heartbeat:
514 chargingStation.ocppRequestService
515 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
516 chargingStation,
517 OCPP16RequestCommand.HEARTBEAT,
518 undefined,
519 {
520 triggerMessage: true
521 }
522 )
523 .catch(errorHandler)
524 break
525 case OCPP16MessageTrigger.StatusNotification:
526 if (connectorId != null) {
527 chargingStation.ocppRequestService
528 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
529 chargingStation,
530 OCPP16RequestCommand.STATUS_NOTIFICATION,
531 {
532 connectorId,
533 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
534 status: chargingStation.getConnectorStatus(connectorId)?.status
535 },
536 {
537 triggerMessage: true
538 }
539 )
540 .catch(errorHandler)
541 } else if (chargingStation.hasEvses) {
542 for (const evseStatus of chargingStation.evses.values()) {
543 for (const [id, connectorStatus] of evseStatus.connectors) {
544 chargingStation.ocppRequestService
545 .requestHandler<
546 OCPP16StatusNotificationRequest,
547 OCPP16StatusNotificationResponse
548 >(
549 chargingStation,
550 OCPP16RequestCommand.STATUS_NOTIFICATION,
551 {
552 connectorId: id,
553 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
554 status: connectorStatus.status
555 },
556 {
557 triggerMessage: true
558 }
559 )
560 .catch(errorHandler)
561 }
562 }
563 } else {
564 for (const [id, connectorStatus] of chargingStation.connectors) {
565 chargingStation.ocppRequestService
566 .requestHandler<
567 OCPP16StatusNotificationRequest,
568 OCPP16StatusNotificationResponse
569 >(
570 chargingStation,
571 OCPP16RequestCommand.STATUS_NOTIFICATION,
572 {
573 connectorId: id,
574 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
575 status: connectorStatus.status
576 },
577 {
578 triggerMessage: true
579 }
580 )
581 .catch(errorHandler)
582 }
583 }
584 break
585 }
586 }
587 )
588 this.validatePayload = this.validatePayload.bind(this)
589 }
590
591 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
592 chargingStation: ChargingStation,
593 messageId: string,
594 commandName: OCPP16IncomingRequestCommand,
595 commandPayload: ReqType
596 ): Promise<void> {
597 let response: ResType
598 if (
599 chargingStation.stationInfo?.ocppStrictCompliance === true &&
600 chargingStation.inPendingState() &&
601 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
602 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
603 ) {
604 throw new OCPPError(
605 ErrorType.SECURITY_ERROR,
606 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
607 commandPayload,
608 undefined,
609 2
610 )} while the charging station is in pending state on the central server`,
611 commandName,
612 commandPayload
613 )
614 }
615 if (
616 chargingStation.isRegistered() ||
617 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
618 chargingStation.inUnknownState())
619 ) {
620 if (
621 this.incomingRequestHandlers.has(commandName) &&
622 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
623 ) {
624 try {
625 this.validatePayload(chargingStation, commandName, commandPayload)
626 // Call the method to build the response
627 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
628 const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
629 if (isAsyncFunction(incomingRequestHandler)) {
630 response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
631 } else {
632 response = incomingRequestHandler(chargingStation, commandPayload) as ResType
633 }
634 } catch (error) {
635 // Log
636 logger.error(
637 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
638 error
639 )
640 throw error
641 }
642 } else {
643 // Throw exception
644 throw new OCPPError(
645 ErrorType.NOT_IMPLEMENTED,
646 `'${commandName}' is not implemented to handle request PDU ${JSON.stringify(
647 commandPayload,
648 undefined,
649 2
650 )}`,
651 commandName,
652 commandPayload
653 )
654 }
655 } else {
656 throw new OCPPError(
657 ErrorType.SECURITY_ERROR,
658 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
659 commandPayload,
660 undefined,
661 2
662 )} while the charging station is not registered on the central server`,
663 commandName,
664 commandPayload
665 )
666 }
667 // Send the built response
668 await chargingStation.ocppRequestService.sendResponse(
669 chargingStation,
670 messageId,
671 response,
672 commandName
673 )
674 // Emit command name event to allow delayed handling
675 this.emit(commandName, chargingStation, commandPayload, response)
676 }
677
678 private validatePayload (
679 chargingStation: ChargingStation,
680 commandName: OCPP16IncomingRequestCommand,
681 commandPayload: JsonType
682 ): boolean {
683 if (this.payloadValidateFunctions.has(commandName)) {
684 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
685 }
686 logger.warn(
687 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
688 )
689 return false
690 }
691
692 // Simulate charging station restart
693 private handleRequestReset (
694 chargingStation: ChargingStation,
695 commandPayload: ResetRequest
696 ): GenericResponse {
697 const { type } = commandPayload
698 chargingStation
699 .reset(`${type}Reset` as OCPP16StopTransactionReason)
700 .catch(Constants.EMPTY_FUNCTION)
701 logger.info(
702 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
703 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
704 chargingStation.stationInfo!.resetTime!
705 )}`
706 )
707 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
708 }
709
710 private async handleRequestUnlockConnector (
711 chargingStation: ChargingStation,
712 commandPayload: UnlockConnectorRequest
713 ): Promise<UnlockConnectorResponse> {
714 const { connectorId } = commandPayload
715 if (!chargingStation.hasConnector(connectorId)) {
716 logger.error(
717 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
718 )
719 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
720 }
721 if (connectorId === 0) {
722 logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
723 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
724 }
725 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
726 const stopResponse = await chargingStation.stopTransactionOnConnector(
727 connectorId,
728 OCPP16StopTransactionReason.UNLOCK_COMMAND
729 )
730 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
731 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
732 }
733 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
734 }
735 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
736 chargingStation,
737 connectorId,
738 OCPP16ChargePointStatus.Available
739 )
740 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
741 }
742
743 private handleRequestGetConfiguration (
744 chargingStation: ChargingStation,
745 commandPayload: GetConfigurationRequest
746 ): GetConfigurationResponse {
747 const { key } = commandPayload
748 const configurationKey: OCPPConfigurationKey[] = []
749 const unknownKey: string[] = []
750 if (key == null) {
751 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
752 for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
753 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
754 continue
755 }
756 configurationKey.push({
757 key: configKey.key,
758 readonly: configKey.readonly,
759 value: configKey.value
760 })
761 }
762 } else if (isNotEmptyArray(key)) {
763 for (const k of key) {
764 const keyFound = getConfigurationKey(chargingStation, k, true)
765 if (keyFound != null) {
766 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
767 continue
768 }
769 configurationKey.push({
770 key: keyFound.key,
771 readonly: keyFound.readonly,
772 value: keyFound.value
773 })
774 } else {
775 unknownKey.push(k)
776 }
777 }
778 }
779 return {
780 configurationKey,
781 unknownKey
782 }
783 }
784
785 private handleRequestChangeConfiguration (
786 chargingStation: ChargingStation,
787 commandPayload: ChangeConfigurationRequest
788 ): ChangeConfigurationResponse {
789 const { key, value } = commandPayload
790 const keyToChange = getConfigurationKey(chargingStation, key, true)
791 if (keyToChange?.readonly === true) {
792 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED
793 } else if (keyToChange?.readonly === false) {
794 let valueChanged = false
795 if (keyToChange.value !== value) {
796 setConfigurationKeyValue(chargingStation, key, value, true)
797 valueChanged = true
798 }
799 let triggerHeartbeatRestart = false
800 if (
801 (keyToChange.key as OCPP16StandardParametersKey) ===
802 OCPP16StandardParametersKey.HeartBeatInterval &&
803 valueChanged
804 ) {
805 setConfigurationKeyValue(
806 chargingStation,
807 OCPP16StandardParametersKey.HeartbeatInterval,
808 value
809 )
810 triggerHeartbeatRestart = true
811 }
812 if (
813 (keyToChange.key as OCPP16StandardParametersKey) ===
814 OCPP16StandardParametersKey.HeartbeatInterval &&
815 valueChanged
816 ) {
817 setConfigurationKeyValue(
818 chargingStation,
819 OCPP16StandardParametersKey.HeartBeatInterval,
820 value
821 )
822 triggerHeartbeatRestart = true
823 }
824 if (triggerHeartbeatRestart) {
825 chargingStation.restartHeartbeat()
826 }
827 if (
828 (keyToChange.key as OCPP16StandardParametersKey) ===
829 OCPP16StandardParametersKey.WebSocketPingInterval &&
830 valueChanged
831 ) {
832 chargingStation.restartWebSocketPing()
833 }
834 if (keyToChange.reboot === true) {
835 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
836 }
837 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
838 }
839 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
840 }
841
842 private handleRequestSetChargingProfile (
843 chargingStation: ChargingStation,
844 commandPayload: SetChargingProfileRequest
845 ): SetChargingProfileResponse {
846 if (
847 !OCPP16ServiceUtils.checkFeatureProfile(
848 chargingStation,
849 OCPP16SupportedFeatureProfiles.SmartCharging,
850 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
851 )
852 ) {
853 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
854 }
855 const { connectorId, csChargingProfiles } = commandPayload
856 if (!chargingStation.hasConnector(connectorId)) {
857 logger.error(
858 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
859 )
860 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
861 }
862 if (
863 csChargingProfiles.chargingProfilePurpose ===
864 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
865 connectorId !== 0
866 ) {
867 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
868 }
869 if (
870 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
871 connectorId === 0
872 ) {
873 logger.error(
874 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
875 )
876 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
877 }
878 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
879 if (
880 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
881 connectorId > 0 &&
882 connectorStatus?.transactionStarted === false
883 ) {
884 logger.error(
885 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
886 )
887 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
888 }
889 if (
890 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
891 connectorId > 0 &&
892 connectorStatus?.transactionStarted === true &&
893 csChargingProfiles.transactionId !== connectorStatus.transactionId
894 ) {
895 logger.error(
896 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
897 csChargingProfiles.transactionId
898 } than the started transaction id ${connectorStatus.transactionId}`
899 )
900 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
901 }
902 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
903 logger.debug(
904 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
905 csChargingProfiles
906 )
907 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
908 }
909
910 private handleRequestGetCompositeSchedule (
911 chargingStation: ChargingStation,
912 commandPayload: OCPP16GetCompositeScheduleRequest
913 ): OCPP16GetCompositeScheduleResponse {
914 if (
915 !OCPP16ServiceUtils.checkFeatureProfile(
916 chargingStation,
917 OCPP16SupportedFeatureProfiles.SmartCharging,
918 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
919 )
920 ) {
921 return OCPP16Constants.OCPP_RESPONSE_REJECTED
922 }
923 const { connectorId, duration, chargingRateUnit } = commandPayload
924 if (!chargingStation.hasConnector(connectorId)) {
925 logger.error(
926 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
927 )
928 return OCPP16Constants.OCPP_RESPONSE_REJECTED
929 }
930 if (connectorId === 0) {
931 logger.error(
932 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
933 )
934 return OCPP16Constants.OCPP_RESPONSE_REJECTED
935 }
936 if (chargingRateUnit != null) {
937 logger.warn(
938 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
939 )
940 }
941 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
942 if (
943 isEmptyArray(connectorStatus?.chargingProfiles) &&
944 isEmptyArray(chargingStation.getConnectorStatus(0)?.chargingProfiles)
945 ) {
946 return OCPP16Constants.OCPP_RESPONSE_REJECTED
947 }
948 const currentDate = new Date()
949 const compositeScheduleInterval: Interval = {
950 start: currentDate,
951 end: addSeconds(currentDate, duration)
952 }
953 // Get charging profiles sorted by connector id then stack level
954 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
955 chargingStation,
956 connectorId
957 )
958 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
959 let compositeSchedule: OCPP16ChargingSchedule | undefined
960 for (const chargingProfile of chargingProfiles) {
961 if (chargingProfile.chargingSchedule.startSchedule == null) {
962 logger.debug(
963 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
964 chargingProfile.chargingProfileId
965 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
966 )
967 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
968 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
969 }
970 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
971 logger.warn(
972 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
973 chargingProfile.chargingProfileId
974 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
975 )
976 chargingProfile.chargingSchedule.startSchedule = convertToDate(
977 chargingProfile.chargingSchedule.startSchedule
978 )
979 }
980 if (chargingProfile.chargingSchedule.duration == null) {
981 logger.debug(
982 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
983 chargingProfile.chargingProfileId
984 } has no duration defined and will be set to the maximum time allowed`
985 )
986 // OCPP specifies that if duration is not defined, it should be infinite
987 chargingProfile.chargingSchedule.duration = differenceInSeconds(
988 maxTime,
989 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
990 chargingProfile.chargingSchedule.startSchedule!
991 )
992 }
993 if (
994 !prepareChargingProfileKind(
995 connectorStatus,
996 chargingProfile,
997 compositeScheduleInterval.start,
998 chargingStation.logPrefix()
999 )
1000 ) {
1001 continue
1002 }
1003 if (
1004 !canProceedChargingProfile(
1005 chargingProfile,
1006 compositeScheduleInterval.start,
1007 chargingStation.logPrefix()
1008 )
1009 ) {
1010 continue
1011 }
1012 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
1013 previousCompositeSchedule,
1014 chargingProfile.chargingSchedule,
1015 compositeScheduleInterval
1016 )
1017 previousCompositeSchedule = compositeSchedule
1018 }
1019 if (compositeSchedule != null) {
1020 return {
1021 status: GenericStatus.Accepted,
1022 scheduleStart: compositeSchedule.startSchedule,
1023 connectorId,
1024 chargingSchedule: compositeSchedule
1025 }
1026 }
1027 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1028 }
1029
1030 private handleRequestClearChargingProfile (
1031 chargingStation: ChargingStation,
1032 commandPayload: OCPP16ClearChargingProfileRequest
1033 ): OCPP16ClearChargingProfileResponse {
1034 if (
1035 !OCPP16ServiceUtils.checkFeatureProfile(
1036 chargingStation,
1037 OCPP16SupportedFeatureProfiles.SmartCharging,
1038 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
1039 )
1040 ) {
1041 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1042 }
1043 const { connectorId } = commandPayload
1044 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1045 if (!chargingStation.hasConnector(connectorId!)) {
1046 logger.error(
1047 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
1048 )
1049 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1050 }
1051 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1052 const connectorStatus = chargingStation.getConnectorStatus(connectorId!)
1053 if (connectorId != null && isNotEmptyArray(connectorStatus?.chargingProfiles)) {
1054 connectorStatus.chargingProfiles = []
1055 logger.debug(
1056 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
1057 )
1058 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1059 }
1060 if (connectorId == null) {
1061 let clearedCP = false
1062 if (chargingStation.hasEvses) {
1063 for (const evseStatus of chargingStation.evses.values()) {
1064 for (const status of evseStatus.connectors.values()) {
1065 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
1066 chargingStation,
1067 commandPayload,
1068 status.chargingProfiles
1069 )
1070 }
1071 }
1072 } else {
1073 for (const id of chargingStation.connectors.keys()) {
1074 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
1075 chargingStation,
1076 commandPayload,
1077 chargingStation.getConnectorStatus(id)?.chargingProfiles
1078 )
1079 }
1080 }
1081 if (clearedCP) {
1082 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1083 }
1084 }
1085 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1086 }
1087
1088 private async handleRequestChangeAvailability (
1089 chargingStation: ChargingStation,
1090 commandPayload: OCPP16ChangeAvailabilityRequest
1091 ): Promise<OCPP16ChangeAvailabilityResponse> {
1092 const { connectorId, type } = commandPayload
1093 if (!chargingStation.hasConnector(connectorId)) {
1094 logger.error(
1095 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
1096 )
1097 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
1098 }
1099 const chargePointStatus: OCPP16ChargePointStatus =
1100 type === OCPP16AvailabilityType.Operative
1101 ? OCPP16ChargePointStatus.Available
1102 : OCPP16ChargePointStatus.Unavailable
1103 if (connectorId === 0) {
1104 let response: OCPP16ChangeAvailabilityResponse | undefined
1105 if (chargingStation.hasEvses) {
1106 for (const evseStatus of chargingStation.evses.values()) {
1107 response = await OCPP16ServiceUtils.changeAvailability(
1108 chargingStation,
1109 [...evseStatus.connectors.keys()],
1110 chargePointStatus,
1111 type
1112 )
1113 }
1114 } else {
1115 response = await OCPP16ServiceUtils.changeAvailability(
1116 chargingStation,
1117 [...chargingStation.connectors.keys()],
1118 chargePointStatus,
1119 type
1120 )
1121 }
1122 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1123 return response!
1124 } else if (
1125 connectorId > 0 &&
1126 (chargingStation.isChargingStationAvailable() ||
1127 (!chargingStation.isChargingStationAvailable() &&
1128 type === OCPP16AvailabilityType.Inoperative))
1129 ) {
1130 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
1131 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1132 chargingStation.getConnectorStatus(connectorId)!.availability = type
1133 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
1134 }
1135 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1136 chargingStation.getConnectorStatus(connectorId)!.availability = type
1137 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1138 chargingStation,
1139 connectorId,
1140 chargePointStatus
1141 )
1142 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
1143 }
1144 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
1145 }
1146
1147 private async handleRequestRemoteStartTransaction (
1148 chargingStation: ChargingStation,
1149 commandPayload: RemoteStartTransactionRequest
1150 ): Promise<GenericResponse> {
1151 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1152 if (!chargingStation.hasConnector(transactionConnectorId)) {
1153 return await this.notifyRemoteStartTransactionRejected(
1154 chargingStation,
1155 transactionConnectorId,
1156 idTag
1157 )
1158 }
1159 if (
1160 !chargingStation.isChargingStationAvailable() ||
1161 !chargingStation.isConnectorAvailable(transactionConnectorId)
1162 ) {
1163 return await this.notifyRemoteStartTransactionRejected(
1164 chargingStation,
1165 transactionConnectorId,
1166 idTag
1167 )
1168 }
1169 // idTag authorization check required
1170 if (
1171 chargingStation.getAuthorizeRemoteTxRequests() &&
1172 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
1173 ) {
1174 return await this.notifyRemoteStartTransactionRejected(
1175 chargingStation,
1176 transactionConnectorId,
1177 idTag
1178 )
1179 }
1180 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1181 chargingStation,
1182 transactionConnectorId,
1183 OCPP16ChargePointStatus.Preparing
1184 )
1185 if (
1186 chargingProfile != null &&
1187 !this.setRemoteStartTransactionChargingProfile(
1188 chargingStation,
1189 transactionConnectorId,
1190 chargingProfile
1191 )
1192 ) {
1193 return await this.notifyRemoteStartTransactionRejected(
1194 chargingStation,
1195 transactionConnectorId,
1196 idTag
1197 )
1198 }
1199 logger.debug(
1200 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on connector id ${transactionConnectorId}, idTag '${idTag}'`
1201 )
1202 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1203 }
1204
1205 private async notifyRemoteStartTransactionRejected (
1206 chargingStation: ChargingStation,
1207 connectorId: number,
1208 idTag: string
1209 ): Promise<GenericResponse> {
1210 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1211 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
1212 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1213 chargingStation,
1214 connectorId,
1215 OCPP16ChargePointStatus.Available
1216 )
1217 }
1218 logger.debug(
1219 `${chargingStation.logPrefix()} Remote start transaction REJECTED on connector id ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`
1220 )
1221 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1222 }
1223
1224 private setRemoteStartTransactionChargingProfile (
1225 chargingStation: ChargingStation,
1226 connectorId: number,
1227 chargingProfile: OCPP16ChargingProfile
1228 ): boolean {
1229 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1230 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1231 logger.debug(
1232 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}: %j`,
1233 chargingProfile
1234 )
1235 return true
1236 }
1237 logger.debug(
1238 `${chargingStation.logPrefix()} Not allowed to set ${
1239 chargingProfile.chargingProfilePurpose
1240 } charging profile(s) at remote start transaction`
1241 )
1242 return false
1243 }
1244
1245 private handleRequestRemoteStopTransaction (
1246 chargingStation: ChargingStation,
1247 commandPayload: RemoteStopTransactionRequest
1248 ): GenericResponse {
1249 const { transactionId } = commandPayload
1250 if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
1251 logger.debug(
1252 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
1253 )
1254 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1255 }
1256 logger.debug(
1257 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
1258 )
1259 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1260 }
1261
1262 private handleRequestUpdateFirmware (
1263 chargingStation: ChargingStation,
1264 commandPayload: OCPP16UpdateFirmwareRequest
1265 ): OCPP16UpdateFirmwareResponse {
1266 if (
1267 !OCPP16ServiceUtils.checkFeatureProfile(
1268 chargingStation,
1269 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1270 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1271 )
1272 ) {
1273 logger.warn(
1274 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1275 )
1276 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1277 }
1278 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1279 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1280 const { retrieveDate } = commandPayload
1281 if (chargingStation.stationInfo?.firmwareStatus !== OCPP16FirmwareStatus.Installed) {
1282 logger.warn(
1283 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1284 )
1285 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1286 }
1287 const now = Date.now()
1288 if (retrieveDate.getTime() <= now) {
1289 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1290 } else {
1291 setTimeout(() => {
1292 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1293 }, retrieveDate.getTime() - now)
1294 }
1295 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1296 }
1297
1298 private async updateFirmwareSimulation (
1299 chargingStation: ChargingStation,
1300 maxDelay = 30,
1301 minDelay = 15
1302 ): Promise<void> {
1303 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1304 return
1305 }
1306 if (chargingStation.hasEvses) {
1307 for (const [evseId, evseStatus] of chargingStation.evses) {
1308 if (evseId > 0) {
1309 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1310 if (connectorStatus.transactionStarted === false) {
1311 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1312 chargingStation,
1313 connectorId,
1314 OCPP16ChargePointStatus.Unavailable
1315 )
1316 }
1317 }
1318 }
1319 }
1320 } else {
1321 for (const connectorId of chargingStation.connectors.keys()) {
1322 if (
1323 connectorId > 0 &&
1324 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1325 ) {
1326 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1327 chargingStation,
1328 connectorId,
1329 OCPP16ChargePointStatus.Unavailable
1330 )
1331 }
1332 }
1333 }
1334 await chargingStation.ocppRequestService.requestHandler<
1335 OCPP16FirmwareStatusNotificationRequest,
1336 OCPP16FirmwareStatusNotificationResponse
1337 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1338 status: OCPP16FirmwareStatus.Downloading
1339 })
1340 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1341 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1342 if (
1343 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1344 OCPP16FirmwareStatus.DownloadFailed
1345 ) {
1346 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1347 await chargingStation.ocppRequestService.requestHandler<
1348 OCPP16FirmwareStatusNotificationRequest,
1349 OCPP16FirmwareStatusNotificationResponse
1350 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1351 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1352 })
1353 chargingStation.stationInfo.firmwareStatus =
1354 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1355 return
1356 }
1357 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1358 await chargingStation.ocppRequestService.requestHandler<
1359 OCPP16FirmwareStatusNotificationRequest,
1360 OCPP16FirmwareStatusNotificationResponse
1361 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1362 status: OCPP16FirmwareStatus.Downloaded
1363 })
1364 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1365 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1366 let wasTransactionsStarted = false
1367 let transactionsStarted: boolean
1368 do {
1369 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1370 if (runningTransactions > 0) {
1371 const waitTime = secondsToMilliseconds(15)
1372 logger.debug(
1373 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1374 waitTime
1375 )} before continuing firmware update simulation`
1376 )
1377 await sleep(waitTime)
1378 transactionsStarted = true
1379 wasTransactionsStarted = true
1380 } else {
1381 if (chargingStation.hasEvses) {
1382 for (const [evseId, evseStatus] of chargingStation.evses) {
1383 if (evseId > 0) {
1384 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1385 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1386 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1387 chargingStation,
1388 connectorId,
1389 OCPP16ChargePointStatus.Unavailable
1390 )
1391 }
1392 }
1393 }
1394 }
1395 } else {
1396 for (const connectorId of chargingStation.connectors.keys()) {
1397 if (
1398 connectorId > 0 &&
1399 chargingStation.getConnectorStatus(connectorId)?.status !==
1400 OCPP16ChargePointStatus.Unavailable
1401 ) {
1402 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1403 chargingStation,
1404 connectorId,
1405 OCPP16ChargePointStatus.Unavailable
1406 )
1407 }
1408 }
1409 }
1410 transactionsStarted = false
1411 }
1412 } while (transactionsStarted)
1413 !wasTransactionsStarted &&
1414 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))))
1415 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1416 return
1417 }
1418 await chargingStation.ocppRequestService.requestHandler<
1419 OCPP16FirmwareStatusNotificationRequest,
1420 OCPP16FirmwareStatusNotificationResponse
1421 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1422 status: OCPP16FirmwareStatus.Installing
1423 })
1424 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1425 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1426 if (
1427 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1428 OCPP16FirmwareStatus.InstallationFailed
1429 ) {
1430 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1431 await chargingStation.ocppRequestService.requestHandler<
1432 OCPP16FirmwareStatusNotificationRequest,
1433 OCPP16FirmwareStatusNotificationResponse
1434 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1435 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1436 })
1437 chargingStation.stationInfo.firmwareStatus =
1438 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1439 return
1440 }
1441 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1442 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)))
1443 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1444 }
1445 }
1446
1447 private async handleRequestGetDiagnostics (
1448 chargingStation: ChargingStation,
1449 commandPayload: GetDiagnosticsRequest
1450 ): Promise<GetDiagnosticsResponse> {
1451 if (
1452 !OCPP16ServiceUtils.checkFeatureProfile(
1453 chargingStation,
1454 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1455 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1456 )
1457 ) {
1458 logger.warn(
1459 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1460 )
1461 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1462 }
1463 const { location } = commandPayload
1464 const uri = new URL(location)
1465 if (uri.protocol.startsWith('ftp:')) {
1466 let ftpClient: Client | undefined
1467 try {
1468 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1469 .filter(file => file.endsWith('.log'))
1470 .map(file => join('./', file))
1471 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1472 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1473 ftpClient = new Client()
1474 const accessResponse = await ftpClient.access({
1475 host: uri.host,
1476 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1477 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1478 ...(isNotEmptyString(uri.password) && { password: uri.password })
1479 })
1480 let uploadResponse: FTPResponse | undefined
1481 if (accessResponse.code === 220) {
1482 ftpClient.trackProgress(info => {
1483 logger.info(
1484 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1485 info.bytes / 1024
1486 } bytes transferred from diagnostics archive ${info.name}`
1487 )
1488 chargingStation.ocppRequestService
1489 .requestHandler<
1490 OCPP16DiagnosticsStatusNotificationRequest,
1491 OCPP16DiagnosticsStatusNotificationResponse
1492 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1493 status: OCPP16DiagnosticsStatus.Uploading
1494 })
1495 .catch(error => {
1496 logger.error(
1497 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1498 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1499 }'`,
1500 error
1501 )
1502 })
1503 })
1504 uploadResponse = await ftpClient.uploadFrom(
1505 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1506 `${uri.pathname}${diagnosticsArchive}`
1507 )
1508 if (uploadResponse.code === 226) {
1509 await chargingStation.ocppRequestService.requestHandler<
1510 OCPP16DiagnosticsStatusNotificationRequest,
1511 OCPP16DiagnosticsStatusNotificationResponse
1512 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1513 status: OCPP16DiagnosticsStatus.Uploaded
1514 })
1515 ftpClient.close()
1516 return { fileName: diagnosticsArchive }
1517 }
1518 throw new OCPPError(
1519 ErrorType.GENERIC_ERROR,
1520 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1521 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1522 )
1523 }
1524 throw new OCPPError(
1525 ErrorType.GENERIC_ERROR,
1526 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1527 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1528 )
1529 } catch (error) {
1530 await chargingStation.ocppRequestService.requestHandler<
1531 OCPP16DiagnosticsStatusNotificationRequest,
1532 OCPP16DiagnosticsStatusNotificationResponse
1533 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1534 status: OCPP16DiagnosticsStatus.UploadFailed
1535 })
1536 ftpClient?.close()
1537 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1538 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1539 chargingStation,
1540 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1541 error as Error,
1542 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1543 )!
1544 }
1545 } else {
1546 logger.error(
1547 `${chargingStation.logPrefix()} Unsupported protocol ${
1548 uri.protocol
1549 } to transfer the diagnostic logs archive`
1550 )
1551 await chargingStation.ocppRequestService.requestHandler<
1552 OCPP16DiagnosticsStatusNotificationRequest,
1553 OCPP16DiagnosticsStatusNotificationResponse
1554 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1555 status: OCPP16DiagnosticsStatus.UploadFailed
1556 })
1557 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1558 }
1559 }
1560
1561 private handleRequestTriggerMessage (
1562 chargingStation: ChargingStation,
1563 commandPayload: OCPP16TriggerMessageRequest
1564 ): OCPP16TriggerMessageResponse {
1565 const { requestedMessage, connectorId } = commandPayload
1566 if (
1567 !OCPP16ServiceUtils.checkFeatureProfile(
1568 chargingStation,
1569 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1570 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1571 ) ||
1572 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1573 ) {
1574 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1575 }
1576 if (
1577 !OCPP16ServiceUtils.isConnectorIdValid(
1578 chargingStation,
1579 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1580 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1581 connectorId!
1582 )
1583 ) {
1584 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1585 }
1586 switch (requestedMessage) {
1587 case OCPP16MessageTrigger.BootNotification:
1588 case OCPP16MessageTrigger.Heartbeat:
1589 case OCPP16MessageTrigger.StatusNotification:
1590 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1591 default:
1592 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1593 }
1594 }
1595
1596 private handleRequestDataTransfer (
1597 chargingStation: ChargingStation,
1598 commandPayload: OCPP16DataTransferRequest
1599 ): OCPP16DataTransferResponse {
1600 const { vendorId } = commandPayload
1601 try {
1602 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1603 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1604 }
1605 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1606 } catch (error) {
1607 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1608 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1609 chargingStation,
1610 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1611 error as Error,
1612 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1613 )!
1614 }
1615 }
1616
1617 private async handleRequestReserveNow (
1618 chargingStation: ChargingStation,
1619 commandPayload: OCPP16ReserveNowRequest
1620 ): Promise<OCPP16ReserveNowResponse> {
1621 if (
1622 !OCPP16ServiceUtils.checkFeatureProfile(
1623 chargingStation,
1624 OCPP16SupportedFeatureProfiles.Reservation,
1625 OCPP16IncomingRequestCommand.RESERVE_NOW
1626 )
1627 ) {
1628 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1629 }
1630 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1631 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
1632 const { reservationId, idTag, connectorId } = commandPayload
1633 let response: OCPP16ReserveNowResponse
1634 try {
1635 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1636 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1637 }
1638 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1639 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1640 }
1641 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1642 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1643 }
1644 await removeExpiredReservations(chargingStation)
1645 switch (chargingStation.getConnectorStatus(connectorId)?.status) {
1646 case OCPP16ChargePointStatus.Faulted:
1647 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1648 break
1649 case OCPP16ChargePointStatus.Preparing:
1650 case OCPP16ChargePointStatus.Charging:
1651 case OCPP16ChargePointStatus.SuspendedEV:
1652 case OCPP16ChargePointStatus.SuspendedEVSE:
1653 case OCPP16ChargePointStatus.Finishing:
1654 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1655 break
1656 case OCPP16ChargePointStatus.Unavailable:
1657 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1658 break
1659 case OCPP16ChargePointStatus.Reserved:
1660 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1661 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1662 break
1663 }
1664 // eslint-disable-next-line no-fallthrough
1665 default:
1666 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1667 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1668 break
1669 }
1670 await chargingStation.addReservation({
1671 id: commandPayload.reservationId,
1672 ...commandPayload
1673 })
1674 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1675 break
1676 }
1677 return response
1678 } catch (error) {
1679 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1680 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1681 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1682 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1683 chargingStation,
1684 OCPP16IncomingRequestCommand.RESERVE_NOW,
1685 error as Error,
1686 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1687 )!
1688 }
1689 }
1690
1691 private async handleRequestCancelReservation (
1692 chargingStation: ChargingStation,
1693 commandPayload: OCPP16CancelReservationRequest
1694 ): Promise<GenericResponse> {
1695 if (
1696 !OCPP16ServiceUtils.checkFeatureProfile(
1697 chargingStation,
1698 OCPP16SupportedFeatureProfiles.Reservation,
1699 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1700 )
1701 ) {
1702 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1703 }
1704 try {
1705 const { reservationId } = commandPayload
1706 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1707 if (reservation == null) {
1708 logger.debug(
1709 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1710 )
1711 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1712 }
1713 await chargingStation.removeReservation(
1714 reservation,
1715 ReservationTerminationReason.RESERVATION_CANCELED
1716 )
1717 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1718 } catch (error) {
1719 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1720 return this.handleIncomingRequestError<GenericResponse>(
1721 chargingStation,
1722 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1723 error as Error,
1724 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1725 )!
1726 }
1727 }
1728 }