fix: send preparing connector status before `StartTransaction`
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
index 51a9948eec74c033c926ac3a78556fd822b4c802..02755c4ec3da6b5d90e2bcb97668e2204ea55450 100644 (file)
@@ -1,3 +1,4 @@
+import { randomInt } from 'node:crypto'
 import { readFileSync } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
@@ -5,9 +6,6 @@ import { fileURLToPath } from 'node:url'
 import type { DefinedError, ErrorObject, JSONSchemaType } from 'ajv'
 import { isDate } from 'date-fns'
 
-import { OCPP16Constants } from './1.6/OCPP16Constants.js'
-import { OCPP20Constants } from './2.0/OCPP20Constants.js'
-import { OCPPConstants } from './OCPPConstants.js'
 import {
   type ChargingStation,
   getConfigurationKey,
@@ -20,7 +18,8 @@ import {
   type AuthorizeResponse,
   ChargePointErrorCode,
   ChargingStationEvents,
-  type ConnectorStatusEnum,
+  type ConnectorStatus,
+  ConnectorStatusEnum,
   CurrentType,
   ErrorType,
   FileType,
@@ -51,23 +50,25 @@ import {
 import {
   ACElectricUtils,
   Constants,
-  DCElectricUtils,
   convertToFloat,
   convertToInt,
+  DCElectricUtils,
   getRandomFloatFluctuatedRounded,
   getRandomFloatRounded,
-  getRandomInteger,
   handleFileException,
   isNotEmptyArray,
   isNotEmptyString,
-  logPrefix,
   logger,
+  logPrefix,
   max,
   min,
   roundTo
 } from '../../utils/index.js'
+import { OCPP16Constants } from './1.6/OCPP16Constants.js'
+import { OCPP20Constants } from './2.0/OCPP20Constants.js'
+import { OCPPConstants } from './OCPPConstants.js'
 
-export const getMessageTypeString = (messageType: MessageType): string => {
+export const getMessageTypeString = (messageType: MessageType | undefined): string => {
   switch (messageType) {
     case MessageType.CALL_MESSAGE:
       return 'request'
@@ -80,7 +81,7 @@ export const getMessageTypeString = (messageType: MessageType): string => {
   }
 }
 
-export const buildStatusNotificationRequest = (
+const buildStatusNotificationRequest = (
   chargingStation: ChargingStation,
   connectorId: number,
   status: ConnectorStatusEnum,
@@ -120,9 +121,12 @@ export const isIdTagAuthorized = async (
       `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur`
     )
   }
-  if (chargingStation.getLocalAuthListEnabled() && isIdTagLocalAuthorized(chargingStation, idTag)) {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
+  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+  if (
+    connectorStatus != null &&
+    chargingStation.getLocalAuthListEnabled() &&
+    isIdTagLocalAuthorized(chargingStation, idTag)
+  ) {
     connectorStatus.localAuthorizeIdTag = idTag
     connectorStatus.idTagLocalAuthorized = true
     return true
@@ -139,7 +143,7 @@ const isIdTagLocalAuthorized = (chargingStation: ChargingStation, idTag: string)
       chargingStation.idTagsCache
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         .getIdTags(getIdTagsFile(chargingStation.stationInfo!)!)
-        ?.find((tag) => tag === idTag)
+        ?.find(tag => tag === idTag)
     )
   )
 }
@@ -191,24 +195,35 @@ export const sendAndSetConnectorStatus = async (
   })
 }
 
+export const restoreConnectorStatus = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  connectorStatus: ConnectorStatus | undefined
+): Promise<void> => {
+  if (connectorStatus?.reservation != null) {
+    await sendAndSetConnectorStatus(chargingStation, connectorId, ConnectorStatusEnum.Reserved)
+  } else if (connectorStatus?.status !== ConnectorStatusEnum.Available) {
+    await sendAndSetConnectorStatus(chargingStation, connectorId, ConnectorStatusEnum.Available)
+  }
+}
+
 const checkConnectorStatusTransition = (
   chargingStation: ChargingStation,
   connectorId: number,
   status: ConnectorStatusEnum
 ): boolean => {
-  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-  const fromStatus = chargingStation.getConnectorStatus(connectorId)!.status
+  const fromStatus = chargingStation.getConnectorStatus(connectorId)?.status
   let transitionAllowed = false
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16:
       if (
         (connectorId === 0 &&
           OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex(
-            (transition) => transition.from === fromStatus && transition.to === status
+            transition => transition.from === fromStatus && transition.to === status
           ) !== -1) ||
         (connectorId > 0 &&
           OCPP16Constants.ChargePointStatusConnectorTransitions.findIndex(
-            (transition) => transition.from === fromStatus && transition.to === status
+            transition => transition.from === fromStatus && transition.to === status
           ) !== -1)
       ) {
         transitionAllowed = true
@@ -219,11 +234,11 @@ const checkConnectorStatusTransition = (
       if (
         (connectorId === 0 &&
           OCPP20Constants.ChargingStationStatusTransitions.findIndex(
-            (transition) => transition.from === fromStatus && transition.to === status
+            transition => transition.from === fromStatus && transition.to === status
           ) !== -1) ||
         (connectorId > 0 &&
           OCPP20Constants.ConnectorStatusTransitions.findIndex(
-            (transition) => transition.from === fromStatus && transition.to === status
+            transition => transition.from === fromStatus && transition.to === status
           ) !== -1)
       ) {
         transitionAllowed = true
@@ -239,8 +254,7 @@ const checkConnectorStatusTransition = (
       `${chargingStation.logPrefix()} OCPP ${
         chargingStation.stationInfo.ocppVersion
       } connector id ${connectorId} status transition from '${
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        chargingStation.getConnectorStatus(connectorId)!.status
+        chargingStation.getConnectorStatus(connectorId)?.status
       }' to '${status}' is not allowed`
     )
   }
@@ -283,7 +297,7 @@ export const buildMeterValue = (
             parseInt(socSampledValueTemplate.value),
             socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
           )
-          : getRandomInteger(socMaximumValue, socMinimumValue)
+          : randomInt(socMinimumValue, socMaximumValue)
         meterValue.sampledValue.push(
           buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
         )
@@ -442,7 +456,6 @@ export const buildMeterValue = (
         }
       }
       if (powerSampledValueTemplate != null) {
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand)
         const errMsg = `MeterValues measurand ${
           powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
@@ -490,7 +503,7 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L1?.value,
+                    powerPerPhaseSampledValueTemplates.L1.value,
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
@@ -499,7 +512,7 @@ export const buildMeterValue = (
                       fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
-                  powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                  powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -508,7 +521,7 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L2?.value,
+                    powerPerPhaseSampledValueTemplates.L2.value,
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
@@ -517,7 +530,7 @@ export const buildMeterValue = (
                       fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
-                  powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                  powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -526,7 +539,7 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L3?.value,
+                    powerPerPhaseSampledValueTemplates.L3.value,
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
@@ -535,7 +548,7 @@ export const buildMeterValue = (
                       fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
-                  powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                  powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -756,7 +769,7 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L1?.value,
+                    currentPerPhaseSampledValueTemplates.L1.value,
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
@@ -765,7 +778,7 @@ export const buildMeterValue = (
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
-                  currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                  currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -774,7 +787,7 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L2?.value,
+                    currentPerPhaseSampledValueTemplates.L2.value,
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
@@ -783,7 +796,7 @@ export const buildMeterValue = (
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
-                  currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                  currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -792,7 +805,7 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L3?.value,
+                    currentPerPhaseSampledValueTemplates.L3.value,
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
@@ -801,7 +814,7 @@ export const buildMeterValue = (
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
-                  currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                  currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -930,7 +943,6 @@ export const buildMeterValue = (
       // Energy.Active.Import.Register measurand (default)
       energySampledValueTemplate = getSampledValueTemplate(chargingStation, connectorId)
       if (energySampledValueTemplate != null) {
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand)
         const unitDivider =
           energySampledValueTemplate.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
@@ -1115,7 +1127,7 @@ const getSampledValueTemplate = (
     )
     return
   }
-  const sampledValueTemplates: SampledValueTemplate[] =
+  const sampledValueTemplates =
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     chargingStation.getConnectorStatus(connectorId)!.MeterValues
   for (
@@ -1227,10 +1239,11 @@ const getMeasurandDefaultLocation = (
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 export class OCPPServiceUtils {
-  public static getMessageTypeString = getMessageTypeString
-  public static sendAndSetConnectorStatus = sendAndSetConnectorStatus
-  public static isIdTagAuthorized = isIdTagAuthorized
-  public static buildTransactionEndMeterValue = buildTransactionEndMeterValue
+  public static readonly getMessageTypeString = getMessageTypeString
+  public static readonly sendAndSetConnectorStatus = sendAndSetConnectorStatus
+  public static readonly restoreConnectorStatus = restoreConnectorStatus
+  public static readonly isIdTagAuthorized = isIdTagAuthorized
+  public static readonly buildTransactionEndMeterValue = buildTransactionEndMeterValue
   protected static getSampledValueTemplate = getSampledValueTemplate
   protected static buildSampledValue = buildSampledValue
 
@@ -1238,7 +1251,7 @@ export class OCPPServiceUtils {
     // This is intentional
   }
 
-  public static ajvErrorsToErrorType (errors: ErrorObject[] | null | undefined): ErrorType {
+  public static ajvErrorsToErrorType (errors: ErrorObject[] | undefined | null): ErrorType {
     if (isNotEmptyArray(errors)) {
       for (const error of errors as DefinedError[]) {
         switch (error.keyword) {
@@ -1330,16 +1343,16 @@ export class OCPPServiceUtils {
     return true
   }
 
-  public static convertDateToISOString<T extends JsonType>(obj: T): void {
-    for (const key in obj) {
+  public static convertDateToISOString<T extends JsonType>(object: T): void {
+    for (const key in object) {
       // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-      if (isDate(obj![key])) {
+      if (isDate(object![key])) {
         // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-        (obj![key] as string) = (obj![key] as Date).toISOString()
+        (object![key] as string) = (object![key] as Date).toISOString()
         // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition
-      } else if (typeof obj![key] === 'object' && obj![key] !== null) {
+      } else if (typeof object![key] === 'object' && object![key] !== null) {
         // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-        OCPPServiceUtils.convertDateToISOString<T>(obj![key] as T)
+        OCPPServiceUtils.convertDateToISOString<T>(object![key] as T)
       }
     }
   }