fix: restore connector status reserved only if needed
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPServiceUtils.ts
index 045feade6e88843f486b9ebdc4cf5328f1e3ab54..256544fa50090a5a88319f08fdcbe47c2c845f05 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,
@@ -21,7 +19,7 @@ import {
   ChargePointErrorCode,
   ChargingStationEvents,
   type ConnectorStatus,
-  type ConnectorStatusEnum,
+  ConnectorStatusEnum,
   CurrentType,
   ErrorType,
   FileType,
@@ -37,7 +35,9 @@ import {
   MeterValueMeasurand,
   MeterValuePhase,
   MeterValueUnit,
+  type OCPP16ChargePointStatus,
   type OCPP16StatusNotificationRequest,
+  type OCPP20ConnectorStatusEnumType,
   type OCPP20StatusNotificationRequest,
   OCPPVersion,
   RequestCommand,
@@ -50,24 +50,25 @@ import {
 import {
   ACElectricUtils,
   Constants,
-  DCElectricUtils,
   convertToFloat,
   convertToInt,
+  DCElectricUtils,
   getRandomFloatFluctuatedRounded,
   getRandomFloatRounded,
-  getRandomInteger,
   handleFileException,
   isNotEmptyArray,
   isNotEmptyString,
-  isUndefined,
-  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,
@@ -90,16 +91,17 @@ export const buildStatusNotificationRequest = (
     case OCPPVersion.VERSION_16:
       return {
         connectorId,
-        status,
+        status: status as OCPP16ChargePointStatus,
         errorCode: ChargePointErrorCode.NO_ERROR
       } satisfies OCPP16StatusNotificationRequest
     case OCPPVersion.VERSION_20:
     case OCPPVersion.VERSION_201:
       return {
         timestamp: new Date(),
-        connectorStatus: status,
+        connectorStatus: status as OCPP20ConnectorStatusEnumType,
         connectorId,
-        evseId
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        evseId: evseId!
       } satisfies OCPP20StatusNotificationRequest
     default:
       throw new BaseError('Cannot build status notification payload: OCPP version not supported')
@@ -119,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: 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
@@ -137,8 +142,8 @@ const isIdTagLocalAuthorized = (chargingStation: ChargingStation, idTag: string)
     isNotEmptyString(
       chargingStation.idTagsCache
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        .getIdTags(getIdTagsFile(chargingStation.stationInfo)!)
-        ?.find((tag) => tag === idTag)
+        .getIdTags(getIdTagsFile(chargingStation.stationInfo!)!)
+        ?.find(tag => tag === idTag)
     )
   )
 }
@@ -159,7 +164,7 @@ const isIdTagRemoteAuthorized = async (
           idTag
         }
       )
-    )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
+    ).idTagInfo.status === AuthorizationStatus.ACCEPTED
   )
 }
 
@@ -190,24 +195,38 @@ export const sendAndSetConnectorStatus = async (
   })
 }
 
+export const restoreConnectorStatus = async (
+  chargingStation: ChargingStation,
+  connectorId: number,
+  connectorStatus: ConnectorStatus | undefined
+): Promise<void> => {
+  if (
+    connectorStatus?.reservation != null &&
+    connectorStatus.status !== ConnectorStatusEnum.Reserved
+  ) {
+    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
@@ -218,11 +237,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
@@ -235,10 +254,10 @@ const checkConnectorStatusTransition = (
   }
   if (!transitionAllowed) {
     logger.warn(
-      `${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.logPrefix()} OCPP ${
+        chargingStation.stationInfo.ocppVersion
+      } connector id ${connectorId} status transition from '${
+        chargingStation.getConnectorStatus(connectorId)?.status
       }' to '${status}' is not allowed`
     )
   }
@@ -281,7 +300,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)
         )
@@ -321,7 +340,7 @@ export const buildMeterValue = (
         if (
           chargingStation.getNumberOfPhases() !== 3 ||
           (chargingStation.getNumberOfPhases() === 3 &&
-            chargingStation.stationInfo?.mainVoltageMeterValues === true)
+            chargingStation.stationInfo.mainVoltageMeterValues === true)
         ) {
           meterValue.sampledValue.push(
             buildSampledValue(voltageSampledValueTemplate, voltageMeasurandValue)
@@ -363,7 +382,7 @@ export const buildMeterValue = (
               phaseLineToNeutralValue as MeterValuePhase
             )
           )
-          if (chargingStation.stationInfo?.phaseLineToLineVoltageMeterValues === true) {
+          if (chargingStation.stationInfo.phaseLineToLineVoltageMeterValues === true) {
             const phaseLineToLineValue = `L${phase}-L${
               (phase + 1) % chargingStation.getNumberOfPhases() !== 0
                 ? (phase + 1) % chargingStation.getNumberOfPhases()
@@ -440,18 +459,17 @@ export const buildMeterValue = (
         }
       }
       if (powerSampledValueTemplate != null) {
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand!)
+        checkMeasurandPowerDivider(chargingStation, powerSampledValueTemplate.measurand)
         const errMsg = `MeterValues measurand ${
           powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-        }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
+        }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
           chargingStation.templateFile
         }, cannot calculate ${
           powerSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         } measurand value`
         // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
         const powerMeasurandValues: MeasurandValues = {} as MeasurandValues
-        const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
+        const unitDivider = powerSampledValueTemplate.unit === MeterValueUnit.KILO_WATT ? 1000 : 1
         const connectorMaximumAvailablePower =
           chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMaximumPower = Math.round(connectorMaximumAvailablePower)
@@ -462,7 +480,7 @@ export const buildMeterValue = (
         const connectorMinimumPowerPerPhase = Math.round(
           connectorMinimumPower / chargingStation.getNumberOfPhases()
         )
-        switch (chargingStation.stationInfo?.currentOutType) {
+        switch (chargingStation.stationInfo.currentOutType) {
           case CurrentType.AC:
             if (chargingStation.getNumberOfPhases() === 3) {
               const defaultFluctuatedPowerPerPhase = isNotEmptyString(
@@ -475,7 +493,7 @@ export const buildMeterValue = (
                     connectorMinimumPower / unitDivider,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumPower / unitDivider
                     }
                   ) / chargingStation.getNumberOfPhases(),
@@ -488,16 +506,16 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L1?.value,
+                    powerPerPhaseSampledValueTemplates.L1.value,
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
-                  powerPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                  powerPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -506,16 +524,16 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L2?.value,
+                    powerPerPhaseSampledValueTemplates.L2.value,
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
-                  powerPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                  powerPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -524,16 +542,16 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    powerPerPhaseSampledValueTemplates.L3?.value,
+                    powerPerPhaseSampledValueTemplates.L3.value,
                     connectorMaximumPowerPerPhase / unitDivider,
                     connectorMinimumPowerPerPhase / unitDivider,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumPowerPerPhase / unitDivider
                     }
                   ),
-                  powerPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                  powerPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -567,7 +585,7 @@ export const buildMeterValue = (
                     connectorMinimumPower / unitDivider,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumPower / unitDivider
                     }
                   ),
@@ -595,7 +613,7 @@ export const buildMeterValue = (
                   connectorMinimumPower / unitDivider,
                   {
                     limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        chargingStation.stationInfo.customValueLimitationMeterValues,
                     fallbackValue: connectorMinimumPower / unitDivider
                   }
                 ),
@@ -708,10 +726,10 @@ export const buildMeterValue = (
       }
       if (currentSampledValueTemplate != null) {
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        checkMeasurandPowerDivider(chargingStation, currentSampledValueTemplate.measurand!)
+        checkMeasurandPowerDivider(chargingStation, currentSampledValueTemplate.measurand)
         const errMsg = `MeterValues measurand ${
           currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
-        }: Unknown ${chargingStation.stationInfo?.currentOutType} currentOutType in template file ${
+        }: Unknown ${chargingStation.stationInfo.currentOutType} currentOutType in template file ${
           chargingStation.templateFile
         }, cannot calculate ${
           currentSampledValueTemplate.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
@@ -722,7 +740,7 @@ export const buildMeterValue = (
           chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0
         let connectorMaximumAmperage: number
-        switch (chargingStation.stationInfo?.currentOutType) {
+        switch (chargingStation.stationInfo.currentOutType) {
           case CurrentType.AC:
             connectorMaximumAmperage = ACElectricUtils.amperagePerPhaseFromPower(
               chargingStation.getNumberOfPhases(),
@@ -741,7 +759,7 @@ export const buildMeterValue = (
                     connectorMinimumAmperage,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
@@ -754,16 +772,16 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L1?.value,
+                    currentPerPhaseSampledValueTemplates.L1.value,
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
-                  currentPerPhaseSampledValueTemplates.L1?.fluctuationPercent ??
+                  currentPerPhaseSampledValueTemplates.L1.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -772,16 +790,16 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L2?.value,
+                    currentPerPhaseSampledValueTemplates.L2.value,
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
-                  currentPerPhaseSampledValueTemplates.L2?.fluctuationPercent ??
+                  currentPerPhaseSampledValueTemplates.L2.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -790,16 +808,16 @@ export const buildMeterValue = (
               )
                 ? getRandomFloatFluctuatedRounded(
                   getLimitFromSampledValueTemplateCustomValue(
-                    currentPerPhaseSampledValueTemplates.L3?.value,
+                    currentPerPhaseSampledValueTemplates.L3.value,
                     connectorMaximumAmperage,
                     connectorMinimumAmperage,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
-                  currentPerPhaseSampledValueTemplates.L3?.fluctuationPercent ??
+                  currentPerPhaseSampledValueTemplates.L3.fluctuationPercent ??
                       Constants.DEFAULT_FLUCTUATION_PERCENT
                 )
                 : undefined
@@ -824,7 +842,7 @@ export const buildMeterValue = (
                     connectorMinimumAmperage,
                     {
                       limitationEnabled:
-                          chargingStation.stationInfo?.customValueLimitationMeterValues,
+                          chargingStation.stationInfo.customValueLimitationMeterValues,
                       fallbackValue: connectorMinimumAmperage
                     }
                   ),
@@ -855,7 +873,7 @@ export const buildMeterValue = (
                   connectorMinimumAmperage,
                   {
                     limitationEnabled:
-                        chargingStation.stationInfo?.customValueLimitationMeterValues,
+                        chargingStation.stationInfo.customValueLimitationMeterValues,
                     fallbackValue: connectorMinimumAmperage
                   }
                 ),
@@ -928,10 +946,9 @@ 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!)
+        checkMeasurandPowerDivider(chargingStation, energySampledValueTemplate.measurand)
         const unitDivider =
-          energySampledValueTemplate?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
+          energySampledValueTemplate.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1
         const connectorMaximumAvailablePower =
           chargingStation.getConnectorMaximumAvailablePower(connectorId)
         const connectorMaximumEnergyRounded = roundTo(
@@ -949,7 +966,7 @@ export const buildMeterValue = (
               connectorMaximumEnergyRounded,
               connectorMinimumEnergyRounded,
               {
-                limitationEnabled: chargingStation.stationInfo?.customValueLimitationMeterValues,
+                limitationEnabled: chargingStation.stationInfo.customValueLimitationMeterValues,
                 fallbackValue: connectorMinimumEnergyRounded,
                 unitMultiplier: unitDivider
               }
@@ -1009,7 +1026,7 @@ export const buildMeterValue = (
 export const buildTransactionEndMeterValue = (
   chargingStation: ChargingStation,
   connectorId: number,
-  meterStop: number
+  meterStop: number | undefined
 ): MeterValue => {
   let meterValue: MeterValue
   let sampledValueTemplate: SampledValueTemplate | undefined
@@ -1043,15 +1060,15 @@ export const buildTransactionEndMeterValue = (
 
 const checkMeasurandPowerDivider = (
   chargingStation: ChargingStation,
-  measurandType: MeterValueMeasurand
+  measurandType: MeterValueMeasurand | undefined
 ): void => {
-  if (isUndefined(chargingStation.powerDivider)) {
+  if (chargingStation.powerDivider == null) {
     const errMsg = `MeterValues measurand ${
       measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
     }: powerDivider is undefined`
     logger.error(`${chargingStation.logPrefix()} ${errMsg}`)
     throw new OCPPError(ErrorType.INTERNAL_ERROR, errMsg, RequestCommand.METER_VALUES)
-  } else if (chargingStation?.powerDivider <= 0) {
+  } else if (chargingStation.powerDivider <= 0) {
     const errMsg = `MeterValues measurand ${
       measurandType ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
     }: powerDivider have zero or below value ${chargingStation.powerDivider}`
@@ -1075,7 +1092,7 @@ const getLimitFromSampledValueTemplateCustomValue = (
     ...options
   }
   const parsedValue = parseInt(value ?? '')
-  if (options?.limitationEnabled === true) {
+  if (options.limitationEnabled === true) {
     return max(
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       min((!isNaN(parsedValue) ? parsedValue : Infinity) * options.unitMultiplier!, maxLimit),
@@ -1113,7 +1130,7 @@ const getSampledValueTemplate = (
     )
     return
   }
-  const sampledValueTemplates: SampledValueTemplate[] =
+  const sampledValueTemplates =
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     chargingStation.getConnectorStatus(connectorId)!.MeterValues
   for (
@@ -1173,12 +1190,11 @@ const buildSampledValue = (
   context?: MeterValueContext,
   phase?: MeterValuePhase
 ): SampledValue => {
-  const sampledValueContext = context ?? sampledValueTemplate?.context
+  const sampledValueContext = context ?? sampledValueTemplate.context
   const sampledValueLocation =
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    sampledValueTemplate?.location ?? getMeasurandDefaultLocation(sampledValueTemplate.measurand!)
-  const sampledValuePhase = phase ?? sampledValueTemplate?.phase
-  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+    sampledValueTemplate.location ?? getMeasurandDefaultLocation(sampledValueTemplate.measurand!)
+  const sampledValuePhase = phase ?? sampledValueTemplate.phase
   return {
     ...(sampledValueTemplate.unit != null && {
       unit: sampledValueTemplate.unit
@@ -1188,9 +1204,9 @@ const buildSampledValue = (
       measurand: sampledValueTemplate.measurand
     }),
     ...(sampledValueLocation != null && { location: sampledValueLocation }),
-    ...(value != null && { value: value.toString() }),
+    ...{ value: value.toString() },
     ...(sampledValuePhase != null && { phase: sampledValuePhase })
-  } as SampledValue
+  } satisfies SampledValue
 }
 
 const getMeasurandDefaultLocation = (
@@ -1226,10 +1242,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
 
@@ -1237,7 +1254,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) {
@@ -1269,7 +1286,7 @@ export class OCPPServiceUtils {
       isRequestCommand &&
       chargingStation.stationInfo?.commandsSupport?.outgoingCommands?.[command] != null
     ) {
-      return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command]
+      return chargingStation.stationInfo.commandsSupport.outgoingCommands[command]
     }
     logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`)
     return false
@@ -1288,9 +1305,9 @@ export class OCPPServiceUtils {
       return true
     } else if (
       isIncomingRequestCommand &&
-      chargingStation.stationInfo?.commandsSupport?.incomingCommands?.[command] != null
+      chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] != null
     ) {
-      return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command]
+      return chargingStation.stationInfo.commandsSupport.incomingCommands[command]
     }
     logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`)
     return false
@@ -1307,7 +1324,7 @@ export class OCPPServiceUtils {
       isMessageTrigger &&
       chargingStation.stationInfo?.messageTriggerSupport?.[messageTrigger] != null
     ) {
-      return chargingStation.stationInfo?.messageTriggerSupport[messageTrigger]
+      return chargingStation.stationInfo.messageTriggerSupport[messageTrigger]
     }
     logger.error(
       `${chargingStation.logPrefix()} Unknown incoming OCPP message trigger '${messageTrigger}'`
@@ -1329,16 +1346,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])) {
-        // 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()
+      if (isDate(object![key])) {
         // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
-      } else if (obj![key] !== null && typeof obj![key] === 'object') {
+        (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 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)
       }
     }
   }