From d193a94981cfed2c3d502eeca581ce4e9f0eebe5 Mon Sep 17 00:00:00 2001 From: Julian Buecher Date: Wed, 24 May 2023 23:32:17 +0200 Subject: [PATCH] refactor: rewriting functionalities and added additional helper functions --- src/charging-station/ChargingStation.ts | 221 ++++++++++++++---- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 217 +++++------------ .../ocpp/1.6/OCPP16RequestService.ts | 20 ++ src/types/ocpp/1.6/Requests.ts | 4 +- src/types/ocpp/1.6/Reservation.ts | 9 +- src/types/ocpp/Reservation.ts | 2 +- src/utils/Configuration.ts | 26 ++- src/utils/Constants.ts | 2 + 8 files changed, 287 insertions(+), 214 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index ace7aff1..6f786eab 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -63,8 +63,11 @@ import { MeterValueMeasurand, type MeterValuesRequest, type MeterValuesResponse, + OCPP16AuthorizationStatus, + type OCPP16AuthorizeRequest, + type OCPP16AuthorizeResponse, + OCPP16RequestCommand, OCPP16SupportedFeatureProfiles, - OCPP20ConnectorStatusEnumType, OCPPVersion, type OutgoingRequest, PowerUnits, @@ -84,6 +87,7 @@ import { WebSocketCloseEventStatusCode, type WsOptions, } from '../types'; +import { ReservationTerminationReason } from '../types/ocpp/1.6/Reservation'; import type { Reservation } from '../types/ocpp/Reservation'; import { ACElectricUtils, @@ -137,6 +141,7 @@ export class ChargingStation { private webSocketPingSetInterval!: NodeJS.Timeout; private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel; private reservations?: Reservation[]; + private reservationExpiryDateSetInterval?: NodeJS.Timeout; constructor(index: number, templateFile: string) { this.started = false; @@ -647,6 +652,9 @@ export class ChargingStation { if (this.getEnableStatistics() === true) { this.performanceStatistics?.start(); } + if (this.supportsReservations()) { + this.startReservationExpiryDateSetInterval(); + } this.openWSConnection(); // Monitor charging station template file this.templateFileWatcher = FileUtils.watchJsonFile( @@ -903,7 +911,7 @@ export class ChargingStation { public supportsReservationsOnConnectorId0(): boolean { logger.info( - `Check for reservation support on connector 0 in charging station (CS): ${this.logPrefix()}` + ` ${this.logPrefix()} Check for reservation support on connector 0 in charging station (CS)` ); return ( this.supportsReservations() && @@ -914,57 +922,188 @@ export class ChargingStation { ); } - public addReservation(newReservation: Reservation): void { + public async addReservation(reservation: Reservation): Promise { if (Utils.isNullOrUndefined(this.reservations)) { this.reservations = []; } - const [exists, foundReservation] = this.doesReservationExist(newReservation.reservationId); + const [exists, reservationFound] = this.doesReservationExists(reservation); if (exists) { - this.replaceExistingReservation(foundReservation, newReservation); - } else { - this.reservations.push(newReservation); + await this.removeReservation(reservationFound); + } + this.reservations.push(reservation); + if (reservation.connectorId === 0) { + return; } + this.getConnectorStatus(reservation.connectorId).status = ConnectorStatusEnum.Reserved; + await this.ocppRequestService.requestHandler< + StatusNotificationRequest, + StatusNotificationResponse + >( + this, + RequestCommand.STATUS_NOTIFICATION, + OCPPServiceUtils.buildStatusNotificationRequest( + this, + reservation.connectorId, + ConnectorStatusEnum.Reserved + ) + ); } - public removeReservation(existingReservationId: number): void { - const index = this.reservations.findIndex((res) => res.reservationId === existingReservationId); + public async removeReservation( + reservation: Reservation, + reason?: ReservationTerminationReason + ): Promise { + const sameReservation = (r: Reservation) => r.id === reservation.id; + const index = this.reservations?.findIndex(sameReservation); this.reservations.splice(index, 1); + switch (reason) { + case ReservationTerminationReason.TRANSACTION_STARTED: + // No action needed + break; + case ReservationTerminationReason.CONNECTOR_STATE_CHANGED: + // No action needed + break; + default: // ReservationTerminationReason.EXPIRED, ReservationTerminationReason.CANCELED + this.getConnectorStatus(reservation.connectorId).status = ConnectorStatusEnum.Available; + await this.ocppRequestService.requestHandler< + StatusNotificationRequest, + StatusNotificationResponse + >( + this, + RequestCommand.STATUS_NOTIFICATION, + OCPPServiceUtils.buildStatusNotificationRequest( + this, + reservation.connectorId, + ConnectorStatusEnum.Available + ) + ); + break; + } + } + + public getReservationById(id: number): Reservation { + return this.reservations?.find((reservation) => reservation.id === id); + } + + public getReservationByIdTag(id: string): Reservation { + return this.reservations?.find((reservation) => reservation.idTag === id); + } + + public getReservationByConnectorId(id: number): Reservation { + return this.reservations?.find((reservation) => reservation.connectorId === id); + } + + public doesReservationExists(reservation: Partial): [boolean, Reservation] { + const sameReservation = (r: Reservation) => r.id === reservation.id; + const foundReservation = this.reservations?.find(sameReservation); + return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation]; } - public getReservation(reservationId: number, reservationIndex?: number): Reservation { - if (!Utils.isNullOrUndefined(reservationIndex)) { - return this.reservations[reservationIndex]; + public async isAuthorized( + connectorId: number, + idTag: string, + parentIdTag?: string + ): Promise { + let authorized = false; + const connectorStatus = this.getConnectorStatus(connectorId); + if ( + this.getLocalAuthListEnabled() === true && + this.hasIdTags() === true && + Utils.isNotEmptyString( + this.idTagsCache + .getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo)) + ?.find((tag) => tag === idTag) + ) + ) { + connectorStatus.localAuthorizeIdTag = idTag; + connectorStatus.idTagLocalAuthorized = true; + authorized = true; + } else if (this.getMustAuthorizeAtRemoteStart() === true) { + connectorStatus.authorizeIdTag = idTag; + const authorizeResponse: OCPP16AuthorizeResponse = + await this.ocppRequestService.requestHandler< + OCPP16AuthorizeRequest, + OCPP16AuthorizeResponse + >(this, OCPP16RequestCommand.AUTHORIZE, { + idTag: idTag, + }); + if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { + authorized = true; + } + } else { + logger.warn( + `${this.logPrefix()} The charging station configuration expects authorize at + remote start transaction but local authorization or authorize isn't enabled` + ); } - return this.reservations.find((r) => r.reservationId === reservationId); + return authorized; } - public doesReservationExist( - reservationId: number, - reservation?: Reservation - ): [boolean, Reservation] { - const foundReservation = this.reservations.find( - (r) => r.reservationId === reservationId || r.reservationId === reservation.reservationId + public startReservationExpiryDateSetInterval(customInterval?: number): void { + const interval = + customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL; + logger.info( + `${this.logPrefix()} Reservation expiration date interval is set to ${interval} + and starts on CS now` ); - return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation]; + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.reservationExpiryDateSetInterval = setInterval(async (): Promise => { + if (!Utils.isNullOrUndefined(this.reservations) && !Utils.isEmptyArray(this.reservations)) { + for (const reservation of this.reservations) { + if (reservation.expiryDate.toString() < new Date().toISOString()) { + await this.removeReservation(reservation); + logger.info( + `${this.logPrefix()} Reservation with ID ${ + reservation.id + } reached expiration date and was removed from CS` + ); + } + } + } + }, interval); } - public getReservationByConnectorId(connectorId: number): Reservation { - return this.reservations.find((r) => r.connectorId === connectorId); + public restartReservationExpiryDateSetInterval(): void { + this.stopReservationExpiryDateSetInterval(); + this.startReservationExpiryDateSetInterval(); } - public getAvailableConnector(): Map { - for (const connectorId in this.connectors) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const connector = this.connectors[Utils.convertToInt(connectorId)]; - if ( - this.isConnectorAvailable(Utils.convertToInt(connectorId)) && - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - connector.status === OCPP20ConnectorStatusEnumType.Available - ) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return connector; + public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean { + const reservation = this.getReservationByConnectorId(connectorId); + return Utils.isUndefined(reservation) || reservation.idTag !== idTag; + } + + public isConnectorReservable( + reservationId: number, + connectorId?: number, + idTag?: string + ): boolean { + const [alreadyExists, _] = this.doesReservationExists({ id: reservationId }); + if (alreadyExists) { + return alreadyExists; + } + const userReservedAlready = Utils.isUndefined(this.getReservationByIdTag(idTag)) ? false : true; + const notConnectorZero = Utils.isUndefined(connectorId) ? true : connectorId > 0; + const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0; + return !alreadyExists && !userReservedAlready && notConnectorZero && freeConnectorsAvailable; + } + + private getNumberOfReservableConnectors(): number { + let reservableConnectors = 0; + this.connectors.forEach((connector, id) => { + if (id === 0) { + return; } - } + if (connector.status === ConnectorStatusEnum.Available) { + reservableConnectors++; + } + }); + return reservableConnectors - this.getNumberOfReservationsOnConnectorZero(); + } + + private getNumberOfReservationsOnConnectorZero(): number { + const reservations = this.reservations?.filter((reservation) => reservation.connectorId === 0); + return Utils.isNullOrUndefined(reservations) ? 0 : reservations.length; } private flushMessageBuffer(): void { @@ -994,14 +1133,10 @@ export class ChargingStation { return this.stationInfo.supervisionUrlOcppConfiguration ?? false; } - private replaceExistingReservation( - existingReservation: Reservation, - newReservation: Reservation - ): void { - const existingReservationIndex = this.reservations.findIndex( - (r) => r.reservationId === existingReservation.reservationId - ); - this.reservations.splice(existingReservationIndex, 1, newReservation); + private stopReservationExpiryDateSetInterval(): void { + if (this.reservationExpiryDateSetInterval) { + clearInterval(this.reservationExpiryDateSetInterval); + } } private getSupervisionUrlOcppKey(): string { @@ -1040,6 +1175,7 @@ export class ChargingStation { private getStationInfoFromTemplate(): ChargingStationInfo { const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); ChargingStationUtils.warnTemplateKeysDeprecation( stationTemplate, @@ -1155,6 +1291,7 @@ export class ChargingStation { private initialize(): void { const stationTemplate = this.getTemplateFromFile(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile); this.configurationFile = path.join( path.dirname(this.templateFile.replace('station-templates', 'configurations')), diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 46c12018..59973597 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -37,8 +37,6 @@ import { type JsonObject, type JsonType, OCPP16AuthorizationStatus, - type OCPP16AuthorizeRequest, - type OCPP16AuthorizeResponse, OCPP16AvailabilityType, type OCPP16BootNotificationRequest, type OCPP16BootNotificationResponse, @@ -90,6 +88,7 @@ import type { OCPP16CancelReservationRequest, OCPP16ReserveNowRequest, } from '../../../types/ocpp/1.6/Requests'; +import { ReservationTerminationReason } from '../../../types/ocpp/1.6/Reservation'; import type { OCPP16CancelReservationResponse, OCPP16ReserveNowResponse, @@ -149,6 +148,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)], [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)], [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)], + [OCPP16IncomingRequestCommand.RESERVE_NOW, this.handleRequestReserveNow.bind(this)], + [ + OCPP16IncomingRequestCommand.CANCEL_RESERVATION, + this.handleRequestCancelReservation.bind(this), + ], ]); this.jsonSchemas = new Map>([ [ @@ -271,6 +275,22 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { 'constructor' ), ], + [ + OCPP16IncomingRequestCommand.RESERVE_NOW, + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ReserveNow.json', + moduleName, + 'constructor' + ), + ], + [ + OCPP16IncomingRequestCommand.CANCEL_RESERVATION, + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/CancelReservation.json', + moduleName, + 'constructor' + ), + ], ]); this.validatePayload = this.validatePayload.bind(this) as ( chargingStation: ChargingStation, @@ -810,9 +830,18 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { commandPayload: RemoteStartTransactionRequest ): Promise { const transactionConnectorId = commandPayload.connectorId; - const reserved: boolean = + const reserved = chargingStation.getConnectorStatus(transactionConnectorId).status === OCPP16ChargePointStatus.Reserved; + if ( + reserved && + chargingStation.validateIncomingRequestWithReservation( + transactionConnectorId, + commandPayload.idTag + ) + ) { + return OCPP16Constants.OCPP_RESPONSE_REJECTED; + } if (chargingStation.hasConnector(transactionConnectorId) === false) { return this.notifyRemoteStartTransactionRejected( chargingStation, @@ -821,9 +850,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ); } if ( - chargingStation.isChargingStationAvailable() === false || - chargingStation.isConnectorAvailable(transactionConnectorId) === false || - reserved + !chargingStation.isChargingStationAvailable() || + !chargingStation.isConnectorAvailable(transactionConnectorId) ) { return this.notifyRemoteStartTransactionRejected( chargingStation, @@ -842,8 +870,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId); // Check if authorized if (chargingStation.getAuthorizeRemoteTxRequests() === true) { - const authorized = await this.isAuthorized( - chargingStation, + const authorized = await chargingStation.isAuthorized( transactionConnectorId, commandPayload.idTag ); @@ -862,8 +889,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { idTag: commandPayload.idTag, }; if (reserved) { - startTransactionData['reservationId'] = - chargingStation.getReservationByConnectorId(transactionConnectorId).reservationId; + const reservation = chargingStation.getReservationByConnectorId(transactionConnectorId); + startTransactionData.reservationId = reservation.id; + await chargingStation.removeReservation( + reservation, + ReservationTerminationReason.TRANSACTION_STARTED + ); } if ( ( @@ -894,13 +925,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { commandPayload.idTag ); } - if (reserved) { - await this.handleReservedRemoteStartTransaction( - chargingStation, - transactionConnectorId, - commandPayload - ); - } // No authorization check required, start transaction if ( this.setRemoteStartTransactionChargingProfile( @@ -1512,7 +1536,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { commandPayload: OCPP16ReserveNowRequest ): Promise { const { reservationId, idTag, connectorId } = commandPayload; - let connector: Map; let response: OCPP16ReserveNowResponse; try { if ( @@ -1524,7 +1547,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (connectorId === 0 && !chargingStation.supportsReservationsOnConnectorId0()) { return OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED; } - if (!(await this.isAuthorized(chargingStation, connectorId, commandPayload.idTag))) { + if (!(await chargingStation.isAuthorized(connectorId, idTag))) { return OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED; } switch (chargingStation.getConnectorStatus(connectorId).status) { @@ -1538,43 +1561,31 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { response = OCPPConstants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE; break; case ConnectorStatusEnum.Reserved: - if (Utils.isUndefined(chargingStation.getReservation(commandPayload.reservationId))) { + if (!chargingStation.isConnectorReservable(reservationId, connectorId, idTag)) { response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED; break; } // eslint-disable-next-line no-fallthrough default: - logger.info( - `${chargingStation.logPrefix()} on connector ${connectorId} is now reserved for ${ - commandPayload.idTag - }` - ); - chargingStation.getConnectorStatus(connectorId).status = ConnectorStatusEnum.Reserved; - chargingStation.addReservation({ ...commandPayload }); - await chargingStation.ocppRequestService - .requestHandler( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: chargingStation.getConnectorStatus(connectorId).status, - }, - { - triggerMessage: true, - } - ) - .catch(Constants.EMPTY_FUNCTION); + if (!chargingStation.isConnectorReservable(reservationId)) { + response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED; + break; + } + await chargingStation.addReservation({ + id: commandPayload.reservationId, + ...commandPayload, + }); response = OCPPConstants.OCPP_RESERVATION_RESPONSE_ACCEPTED; break; } return response; } catch (error) { + chargingStation.getConnectorStatus(connectorId).status = ConnectorStatusEnum.Available; return this.handleIncomingRequestError( chargingStation, OCPP16IncomingRequestCommand.RESERVE_NOW, error as Error, - { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED } + { errorResponse: OCPPConstants.OCPP_RESERVATION_RESPONSE_FAULTED } ); } } @@ -1584,133 +1595,23 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { commandPayload: OCPP16CancelReservationRequest ): Promise { try { - const reservationId = commandPayload.reservationId; - const [exists, reservation] = chargingStation.doesReservationExist(reservationId); + const { reservationId } = commandPayload; + const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId }); if (!exists) { logger.error( `${chargingStation.logPrefix()} Reservation with ID ${reservationId} does not exist on charging station` ); - return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED; + return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED; } - chargingStation.getConnectorStatus(reservation.connectorId).status = - ConnectorStatusEnum.Available; - chargingStation.removeReservation(reservation.reservationId); - await chargingStation.ocppRequestService - .requestHandler( - chargingStation, - OCPP16RequestCommand.STATUS_NOTIFICATION, - { - connectorId: reservation.connectorId, - errorCode: OCPP16ChargePointErrorCode.NO_ERROR, - status: chargingStation.getConnectorStatus(reservation.connectorId).status, - }, - { - triggerMessage: true, - } - ) - .catch(Constants.EMPTY_FUNCTION); - return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED; + await chargingStation.removeReservation(reservation); + return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED; } catch (error) { return this.handleIncomingRequestError( chargingStation, OCPP16IncomingRequestCommand.CANCEL_RESERVATION, error as Error, - { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } - ); - } - } - - /** - * Check for authorized access on a connector with given ConnectorId and idTag for the user - * @param {ChargingStation} chargingStation - Charging Station working on incoming request - * @param {number} ConnectorId - Identifier of the connector at the charging station - * @param {string} idTag - Identifier of the user - * @param {string} parentIdTag - Identifier for a group of idTags, which is optional - * @returns {Promise} - 'true' if user is authorized, 'false' otherwise - */ - private async isAuthorized( - chargingStation: ChargingStation, - connectorId: number, - idTag: string, - parentIdTag?: string - ): Promise { - let authorized = false; - const connectorStatus = chargingStation.getConnectorStatus(connectorId); - if ( - chargingStation.getLocalAuthListEnabled() === true && - chargingStation.hasIdTags() === true && - Utils.isNotEmptyString( - chargingStation.idTagsCache - .getIdTags(ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo)) - ?.find((tag) => tag === idTag) - ) - ) { - connectorStatus.localAuthorizeIdTag = idTag; - connectorStatus.idTagLocalAuthorized = true; - authorized = true; - } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) { - connectorStatus.authorizeIdTag = idTag; - const authorizeResponse: OCPP16AuthorizeResponse = - await chargingStation.ocppRequestService.requestHandler< - OCPP16AuthorizeRequest, - OCPP16AuthorizeResponse - >(chargingStation, OCPP16RequestCommand.AUTHORIZE, { - idTag: idTag, - }); - if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { - authorized = true; - } - } else { - logger.warn( - `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled` - ); - } - return authorized; - } - - private async handleReservedRemoteStartTransaction( - chargingStation: ChargingStation, - connectorId: number, - commandPayload: RemoteStartTransactionRequest - ): Promise { - const reservation = chargingStation.getReservationByConnectorId(connectorId); - if ( - !Utils.isUndefined(reservation) && - (await this.isAuthorized(chargingStation, connectorId, commandPayload.idTag)) && - reservation.idTag === commandPayload.idTag - ) { - const remoteStartTransactionLogMsg = `${chargingStation.logPrefix()} Transaction remotely STARTED on ${ - chargingStation.stationInfo.chargingStationId - }#${connectorId.toString()} for idTag '${commandPayload.idTag}'`; - await OCPP16ServiceUtils.sendAndSetConnectorStatus( - chargingStation, - connectorId, - OCPP16ChargePointStatus.Preparing + { errorResponse: OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED } ); - if ( - this.setRemoteStartTransactionChargingProfile( - chargingStation, - connectorId, - commandPayload.chargingProfile - ) === true - ) { - chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted = true; - if ( - ( - await chargingStation.ocppRequestService.requestHandler< - OCPP16StartTransactionRequest, - OCPP16StartTransactionResponse - >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, { - connectorId: connectorId, - idTag: commandPayload.idTag, - reservationId: reservation.reservationId, - }) - ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED - ) { - logger.debug(remoteStartTransactionLogMsg); - return OCPP16Constants.OCPP_RESPONSE_ACCEPTED; - } - } } } } diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts index 7ff1fdb8..b82599b7 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts @@ -24,6 +24,10 @@ import { OCPPVersion, type RequestParams, } from '../../../types'; +import type { + OCPP16CancelReservationRequest, + OCPP16ReserveNowRequest, +} from '../../../types/ocpp/1.6/Requests'; import { Constants, Utils } from '../../../utils'; import { OCPPRequestService } from '../OCPPRequestService'; import type { OCPPResponseService } from '../OCPPResponseService'; @@ -119,6 +123,22 @@ export class OCPP16RequestService extends OCPPRequestService { 'constructor' ), ], + [ + OCPP16RequestCommand.RESERVE_NOW, + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/ReserveNow.json', + moduleName, + 'constructor' + ), + ], + [ + OCPP16RequestCommand.CANCEL_RESERVATION, + OCPP16ServiceUtils.parseJsonSchemaFile( + 'assets/json-schemas/ocpp/1.6/CancelReservation.json', + moduleName, + 'constructor' + ), + ], ]); this.buildRequestPayload = this.buildRequestPayload.bind(this) as ( chargingStation: ChargingStation, diff --git a/src/types/ocpp/1.6/Requests.ts b/src/types/ocpp/1.6/Requests.ts index a04a3f65..80128a27 100644 --- a/src/types/ocpp/1.6/Requests.ts +++ b/src/types/ocpp/1.6/Requests.ts @@ -188,7 +188,7 @@ export interface OCPP16DataTransferRequest extends JsonObject { data?: string; } -export interface OCPP16ReserveNowRequest { +export interface OCPP16ReserveNowRequest extends JsonObject { connectorId: number; expiryDate: Date; idTag: string; @@ -196,6 +196,6 @@ export interface OCPP16ReserveNowRequest { reservationId: number; } -export interface OCPP16CancelReservationRequest { +export interface OCPP16CancelReservationRequest extends JsonObject { reservationId: number; } diff --git a/src/types/ocpp/1.6/Reservation.ts b/src/types/ocpp/1.6/Reservation.ts index 7a78225d..49bebd14 100644 --- a/src/types/ocpp/1.6/Reservation.ts +++ b/src/types/ocpp/1.6/Reservation.ts @@ -1,7 +1,14 @@ export interface OCPP16Reservation { + id: number; connectorId: number; expiryDate: Date; idTag: string; parentIdTag?: string; - reservationId: number; +} + +export enum ReservationTerminationReason { + EXPIRED = 'Expired', + TRANSACTION_STARTED = 'TransactionStarted', + CONNECTOR_STATE_CHANGED = 'ConnectorStateChanged', + CANCELED = 'ReservationCanceled', } diff --git a/src/types/ocpp/Reservation.ts b/src/types/ocpp/Reservation.ts index dd5d8190..b185b874 100644 --- a/src/types/ocpp/Reservation.ts +++ b/src/types/ocpp/Reservation.ts @@ -1,3 +1,3 @@ -import { OCPP16Reservation } from './1.6/Reservation'; +import { type OCPP16Reservation } from './1.6/Reservation'; export type Reservation = OCPP16Reservation; diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index e4215f94..6f53ef3b 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -55,7 +55,9 @@ export class Configuration { public static getUIServer(): UIServerConfiguration { if (Utils.hasOwnProp(Configuration.getConfig(), 'uiWebSocketServer')) { console.error( - chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}` + `${chalk.green(Configuration.logPrefix())} ${chalk.red( + "Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead" + )}` ); } let uiServerConfiguration: UIServerConfiguration = { @@ -137,9 +139,9 @@ export class Configuration { (stationTemplateUrl: StationTemplateUrl) => { if (!Utils.isUndefined(stationTemplateUrl['numberOfStation'])) { console.error( - chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${ - stationTemplateUrl.file - }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}` + `${chalk.green(Configuration.logPrefix())} ${chalk.red( + `Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead` + )}` ); } } @@ -337,15 +339,19 @@ export class Configuration { !Utils.isUndefined((Configuration.getConfig()[sectionName] as Record)[key]) ) { console.error( - chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${ - logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : '' - }}` + `${chalk.green(Configuration.logPrefix())} ${chalk.red( + `Deprecated configuration key '${key}' usage in section '${sectionName}'${ + logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : '' + }` + )}` ); } else if (!Utils.isUndefined(Configuration.getConfig()[key])) { console.error( - chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${ - logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : '' - }}` + `${chalk.green(Configuration.logPrefix())} ${chalk.red( + `Deprecated configuration key '${key}' usage${ + logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : '' + }` + )}` ); } } diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 8608e480..6c641cf6 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -39,6 +39,8 @@ export class Constants { /* This is intentional */ }); + static readonly DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL = 5000; // Ms + private constructor() { // This is intentional } -- 2.34.1