From 0bd926c1e06a31ac2afd1f932857eddce54f1e91 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 26 Jul 2023 23:29:23 +0200 Subject: [PATCH 1/1] refactor: factor out ATG and charging profiles sanity checks MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- .../AutomaticTransactionGenerator.ts | 83 +++++++------- src/charging-station/ChargingStationUtils.ts | 105 +++++++++++------- src/utils/Utils.ts | 8 +- src/utils/index.ts | 2 +- test/utils/Utils.test.ts | 34 +++--- 5 files changed, 129 insertions(+), 103 deletions(-) diff --git a/src/charging-station/AutomaticTransactionGenerator.ts b/src/charging-station/AutomaticTransactionGenerator.ts index 5b27a258..5ed5be42 100644 --- a/src/charging-station/AutomaticTransactionGenerator.ts +++ b/src/charging-station/AutomaticTransactionGenerator.ts @@ -190,46 +190,7 @@ export class AutomaticTransactionGenerator extends AsyncResource { )}`, ); while (this.connectorsStatus.get(connectorId)?.start === true) { - if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) { - this.stopConnector(connectorId); - break; - } - if (this.chargingStation.inAcceptedState() === false) { - logger.error( - `${this.logPrefix( - connectorId, - )} entered in transaction loop while the charging station is not in accepted state`, - ); - this.stopConnector(connectorId); - break; - } - if (this.chargingStation.isChargingStationAvailable() === false) { - logger.info( - `${this.logPrefix( - connectorId, - )} entered in transaction loop while the charging station is unavailable`, - ); - this.stopConnector(connectorId); - break; - } - if (this.chargingStation.isConnectorAvailable(connectorId) === false) { - logger.info( - `${this.logPrefix( - connectorId, - )} entered in transaction loop while the connector ${connectorId} is unavailable`, - ); - this.stopConnector(connectorId); - break; - } - if ( - this.chargingStation.getConnectorStatus(connectorId)?.status === - ConnectorStatusEnum.Unavailable - ) { - logger.info( - `${this.logPrefix( - connectorId, - )} entered in transaction loop while the connector ${connectorId} status is unavailable`, - ); + if (!this.canStartConnector(connectorId)) { this.stopConnector(connectorId); break; } @@ -332,6 +293,48 @@ export class AutomaticTransactionGenerator extends AsyncResource { this.connectorsStatus.get(connectorId)!.start = true; } + private canStartConnector(connectorId: number): boolean { + if (new Date() > this.connectorsStatus.get(connectorId)!.stopDate!) { + return false; + } + if (this.chargingStation.inAcceptedState() === false) { + logger.error( + `${this.logPrefix( + connectorId, + )} entered in transaction loop while the charging station is not in accepted state`, + ); + return false; + } + if (this.chargingStation.isChargingStationAvailable() === false) { + logger.info( + `${this.logPrefix( + connectorId, + )} entered in transaction loop while the charging station is unavailable`, + ); + return false; + } + if (this.chargingStation.isConnectorAvailable(connectorId) === false) { + logger.info( + `${this.logPrefix( + connectorId, + )} entered in transaction loop while the connector ${connectorId} is unavailable`, + ); + return false; + } + if ( + this.chargingStation.getConnectorStatus(connectorId)?.status === + ConnectorStatusEnum.Unavailable + ) { + logger.info( + `${this.logPrefix( + connectorId, + )} entered in transaction loop while the connector ${connectorId} status is unavailable`, + ); + return false; + } + return true; + } + private initializeConnectorsStatus(): void { if (this.chargingStation.hasEvses) { for (const [evseId, evseStatus] of this.chargingStation.evses) { diff --git a/src/charging-station/ChargingStationUtils.ts b/src/charging-station/ChargingStationUtils.ts index b35f0260..39bcbcc9 100644 --- a/src/charging-station/ChargingStationUtils.ts +++ b/src/charging-station/ChargingStationUtils.ts @@ -13,6 +13,7 @@ import { differenceInWeeks, isAfter, isBefore, + isDate, isWithinInterval, toDate, } from 'date-fns'; @@ -56,7 +57,7 @@ import { isNotEmptyString, isNullOrUndefined, isUndefined, - isValidDate, + isValidTime, logger, secureRandom, } from '../utils'; @@ -690,18 +691,6 @@ const getLimitFromChargingProfiles = ( const currentDate = new Date(); const connectorStatus = chargingStation.getConnectorStatus(connectorId); for (const chargingProfile of chargingProfiles) { - if ( - (isValidDate(chargingProfile.validFrom) && - isBefore(currentDate, chargingProfile.validFrom!)) || - (isValidDate(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!)) - ) { - logger.debug( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${ - chargingProfile.chargingProfileId - } is not valid for the current date ${currentDate.toISOString()}`, - ); - continue; - } const chargingSchedule = chargingProfile.chargingSchedule; if (connectorStatus?.transactionStarted && isNullOrUndefined(chargingSchedule?.startSchedule)) { logger.debug( @@ -710,44 +699,30 @@ const getLimitFromChargingProfiles = ( // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction chargingSchedule.startSchedule = connectorStatus?.transactionStart; } - if (!(chargingSchedule?.startSchedule instanceof Date)) { + if (!isDate(chargingSchedule?.startSchedule)) { logger.warn( `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} startSchedule property is not a Date object. Trying to convert it to a Date object`, ); chargingSchedule.startSchedule = convertToDate(chargingSchedule?.startSchedule)!; } - if ( - chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && - isNullOrUndefined(chargingProfile.recurrencyKind) - ) { - logger.error( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`, - ); - continue; - } - if (chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING) { - prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix); - } else if ( - chargingProfile.chargingProfileKind === ChargingProfileKindType.RELATIVE && - connectorStatus?.transactionStarted - ) { - chargingSchedule.startSchedule = connectorStatus?.transactionStart; - } - if (isNullOrUndefined(chargingSchedule?.startSchedule)) { - logger.error( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has (still) no startSchedule defined`, - ); - continue; + switch (chargingProfile.chargingProfileKind) { + case ChargingProfileKindType.RECURRING: + if (!canProceedRecurringChargingProfile(chargingProfile, logPrefix)) { + continue; + } + prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix); + break; + case ChargingProfileKindType.RELATIVE: + connectorStatus?.transactionStarted && + (chargingSchedule.startSchedule = connectorStatus?.transactionStart); + break; } - if (isNullOrUndefined(chargingSchedule?.duration)) { - logger.error( - `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`, - ); + if (!canProceedChargingProfile(chargingProfile, currentDate, logPrefix)) { continue; } // Check if the charging profile is active if ( - isValidDate(chargingSchedule?.startSchedule) && + isValidTime(chargingSchedule?.startSchedule) && isWithinInterval(currentDate, { start: chargingSchedule.startSchedule!, end: addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), @@ -834,6 +809,54 @@ const getLimitFromChargingProfiles = ( } }; +const canProceedChargingProfile = ( + chargingProfile: ChargingProfile, + currentDate: Date, + logPrefix: string, +): boolean => { + if ( + (isValidTime(chargingProfile.validFrom) && isBefore(currentDate, chargingProfile.validFrom!)) || + (isValidTime(chargingProfile.validTo) && isAfter(currentDate, chargingProfile.validTo!)) + ) { + logger.debug( + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${ + chargingProfile.chargingProfileId + } is not valid for the current date ${currentDate.toISOString()}`, + ); + return false; + } + const chargingSchedule = chargingProfile.chargingSchedule; + if (isNullOrUndefined(chargingSchedule?.startSchedule)) { + logger.error( + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has (still) no startSchedule defined`, + ); + return false; + } + if (isNullOrUndefined(chargingSchedule?.duration)) { + logger.error( + `${logPrefix} ${moduleName}.canProceedChargingProfile: Charging profile id ${chargingProfile.chargingProfileId} has no duration defined, not yet supported`, + ); + return false; + } + return true; +}; + +const canProceedRecurringChargingProfile = ( + chargingProfile: ChargingProfile, + logPrefix: string, +): boolean => { + if ( + chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING && + isNullOrUndefined(chargingProfile.recurrencyKind) + ) { + logger.error( + `${logPrefix} ${moduleName}.canProceedRecurringChargingProfile: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`, + ); + return false; + } + return true; +}; + /** * Adjust recurring charging profile startSchedule to the current recurrency time interval if needed * diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index dd6591f8..73bb0ba9 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -60,8 +60,8 @@ export const formatDurationSeconds = (duration: number): string => { return formatDurationMilliSeconds(secondsToMilliseconds(duration)); }; -// More efficient date validation function than the one provided by date-fns -export const isValidDate = (date: unknown): boolean => { +// More efficient time validation function than the one provided by date-fns +export const isValidTime = (date: unknown): boolean => { if (typeof date === 'number') { return !isNaN(date); } else if (isDate(date)) { @@ -76,8 +76,8 @@ export const convertToDate = ( if (isNullOrUndefined(value)) { return value as null | undefined; } - if (value instanceof Date) { - return value; + if (isDate(value)) { + return value as Date; } if (isString(value) || typeof value === 'number') { return new Date(value!); diff --git a/src/utils/index.ts b/src/utils/index.ts index 86649874..1cdd45ee 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -47,7 +47,7 @@ export { isNotEmptyString, isNullOrUndefined, isUndefined, - isValidDate, + isValidTime, logPrefix, promiseWithTimeout, roundTo, diff --git a/test/utils/Utils.test.ts b/test/utils/Utils.test.ts index 0050f65b..67da558e 100644 --- a/test/utils/Utils.test.ts +++ b/test/utils/Utils.test.ts @@ -24,7 +24,7 @@ import { isNullOrUndefined, isObject, isUndefined, - isValidDate, + isValidTime, roundTo, secureRandom, sleep, @@ -63,22 +63,22 @@ describe('Utils test suite', () => { expect(formatDurationSeconds(hoursToSeconds(4380))).toBe('182 days 12 hours'); }); - it('Verify isValidDate()', () => { - expect(isValidDate(undefined)).toBe(false); - expect(isValidDate(null)).toBe(false); - expect(isValidDate('')).toBe(false); - expect(isValidDate({})).toBe(false); - expect(isValidDate([])).toBe(false); - expect(isValidDate(new Map())).toBe(false); - expect(isValidDate(new Set())).toBe(false); - expect(isValidDate(new WeakMap())).toBe(false); - expect(isValidDate(new WeakSet())).toBe(false); - expect(isValidDate(-1)).toBe(true); - expect(isValidDate(0)).toBe(true); - expect(isValidDate(1)).toBe(true); - expect(isValidDate(-0.5)).toBe(true); - expect(isValidDate(0.5)).toBe(true); - expect(isValidDate(new Date())).toBe(true); + it('Verify isValidTime()', () => { + expect(isValidTime(undefined)).toBe(false); + expect(isValidTime(null)).toBe(false); + expect(isValidTime('')).toBe(false); + expect(isValidTime({})).toBe(false); + expect(isValidTime([])).toBe(false); + expect(isValidTime(new Map())).toBe(false); + expect(isValidTime(new Set())).toBe(false); + expect(isValidTime(new WeakMap())).toBe(false); + expect(isValidTime(new WeakSet())).toBe(false); + expect(isValidTime(-1)).toBe(true); + expect(isValidTime(0)).toBe(true); + expect(isValidTime(1)).toBe(true); + expect(isValidTime(-0.5)).toBe(true); + expect(isValidTime(0.5)).toBe(true); + expect(isValidTime(new Date())).toBe(true); }); it('Verify convertToDate()', () => { -- 2.34.1