]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor: align more with specs
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 10 Nov 2025 19:33:52 +0000 (20:33 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 10 Nov 2025 19:33:52 +0000 (20:33 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts

index f924231e3aa4a1bb1c1eb053c4f68f3c88951957..6aeff5f614e4ca60073d3db94347154726432968 100644 (file)
@@ -160,9 +160,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
   >
 
   public constructor () {
-    // if (new.target.name === moduleName) {
-    //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
-    // }
     super(OCPPVersion.VERSION_16)
     this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
       [
@@ -453,7 +450,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
           commandPayload,
           undefined,
           2
-        )} while the charging station is in pending state on the central server`,
+        )} while the charging station is in pending state on the central system`,
         commandName,
         commandPayload
       )
@@ -506,7 +503,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
           commandPayload,
           undefined,
           2
-        )} while the charging station is not registered on the central server`,
+        )} while the charging station is not registered on the central system`,
         commandName,
         commandPayload
       )
@@ -830,7 +827,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       end: addSeconds(currentDate, duration),
       start: currentDate,
     }
-    // FIXME: add and handle charging station charging profiles
     const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
       chargingStation,
       connectorId
@@ -1160,12 +1156,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
   ): GenericResponse {
     const { transactionId } = commandPayload
     if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
-      logger.debug(
+      logger.info(
         `${chargingStation.logPrefix()} ${moduleName}.handleRequestRemoteStopTransaction: Remote stop transaction ACCEPTED for transactionId '${transactionId.toString()}'`
       )
       return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
     }
-    logger.debug(
+    logger.warn(
       `${chargingStation.logPrefix()} ${moduleName}.handleRequestRemoteStopTransaction: Remote stop transaction REJECTED for transactionId '${transactionId.toString()}'`
     )
     return OCPP16Constants.OCPP_RESPONSE_REJECTED
@@ -1269,7 +1265,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       .reset(`${type}Reset` as OCPP16StopTransactionReason)
       .catch(Constants.EMPTY_FUNCTION)
     logger.info(
-      `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
+      `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: ${type} reset request received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         chargingStation.stationInfo!.resetTime!
       )}`
index e2a0417ee759588cd09e408d33ebb2fde843d012..0f2b766b4a9b1c261ef87c0273b693b9edc04d16 100644 (file)
@@ -23,22 +23,66 @@ import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 
 const moduleName = 'OCPP16RequestService'
 
+/**
+ * OCPP 1.6 Request Service
+ *
+ * Handles outgoing OCPP 1.6 requests from the charging station to the central system.
+ * This service is responsible for:
+ * - Building and validating request payloads according to OCPP 1.6 specification
+ * - Managing request-response cycles with proper error handling
+ * - Ensuring message integrity through JSON schema validation
+ * - Providing type-safe interfaces for all supported OCPP 1.6 commands
+ *
+ * Key architectural components:
+ * - Payload validation using AJV schema validators
+ * - Standardized logging with charging station context
+ * - Comprehensive error handling with OCPP-specific error types
+ * - Integration with the broader OCPP service architecture
+ * OCPPRequestService - Base class providing common OCPP functionality
+ */
 export class OCPP16RequestService extends OCPPRequestService {
   protected payloadValidatorFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
 
+  /**
+   * Constructs an OCPP 1.6 Request Service instance
+   *
+   * Initializes the service with OCPP 1.6-specific configurations including:
+   * - JSON schema validators for all supported OCPP 1.6 request commands
+   * - Response service integration for handling command responses
+   * - AJV validation setup with proper error handling
+   * @param ocppResponseService - The response service instance for handling responses
+   */
   public constructor (ocppResponseService: OCPPResponseService) {
-    // if (new.target.name === moduleName) {
-    //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
-    // }
     super(OCPPVersion.VERSION_16, ocppResponseService)
     this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
       OCPP16ServiceUtils.createRequestPayloadConfigs(),
-      OCPP16ServiceUtils.createRequestFactoryOptions(moduleName, 'constructor'),
+      OCPP16ServiceUtils.createRequestPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
+  /**
+   * Handles OCPP 1.6 request processing with full validation and error handling
+   *
+   * This method serves as the main entry point for all outgoing OCPP 1.6 requests.
+   * It performs the following operations:
+   * - Validates that the requested command is supported by the charging station
+   * - Builds and validates the request payload according to OCPP 1.6 schemas
+   * - Sends the request to the central system with proper error handling
+   * - Processes responses with comprehensive logging and error recovery
+   *
+   * The method ensures type safety through generic type parameters while maintaining
+   * backward compatibility with the OCPP 1.6 specification.
+   * @template RequestType - The expected type of the request parameters
+   * @template ResponseType - The expected type of the response from the central system
+   * @param chargingStation - The charging station instance making the request
+   * @param commandName - The OCPP 1.6 command to execute (e.g., 'StartTransaction', 'StopTransaction')
+   * @param commandParams - Optional parameters specific to the command being executed
+   * @param params - Optional request parameters for controlling request behavior
+   * @returns Promise resolving to the typed response from the central system
+   * @throws {OCPPError} When the command is not supported or validation fails
+   */
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
     chargingStation: ChargingStation,
@@ -49,7 +93,6 @@ export class OCPP16RequestService extends OCPPRequestService {
     logger.debug(
       `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Processing '${commandName}' request`
     )
-    // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
     if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
       try {
         logger.debug(
@@ -99,6 +142,25 @@ export class OCPP16RequestService extends OCPPRequestService {
     throw new OCPPError(ErrorType.NOT_SUPPORTED, errorMsg, commandName, commandParams)
   }
 
+  /**
+   * Builds OCPP 1.6 request payloads with command-specific logic and validation
+   *
+   * This private method handles the construction of request payloads for various OCPP 1.6 commands.
+   * It implements command-specific business logic including:
+   * - Connector ID determination and validation
+   * - Energy meter readings for transaction-related commands
+   * - Transaction data aggregation (when enabled)
+   * - IdTag extraction from charging station context
+   * - Automatic timestamp generation for time-sensitive operations
+   *
+   * The method ensures that all required fields are populated according to OCPP 1.6 specification
+   * requirements while handling optional parameters and station-specific configurations.
+   * @template Request - The expected type of the constructed request payload
+   * @param chargingStation - The charging station instance containing context and configuration
+   * @param commandName - The OCPP 1.6 command being processed (e.g., 'StartTransaction', 'StopTransaction')
+   * @param commandParams - Optional parameters provided by the caller for payload construction
+   * @returns The fully constructed and validated request payload ready for transmission
+   */
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   private buildRequestPayload<Request extends JsonType>(
     chargingStation: ChargingStation,
index 78bd2e2c79940c153ced80f272bca629195acccc..b19ced7f94bfb55ec75ad6318e1735c6709afa8b 100644 (file)
@@ -22,7 +22,6 @@ import {
   type OCPP16BootNotificationResponse,
   OCPP16ChargePointStatus,
   OCPP16IncomingRequestCommand,
-  type OCPP16MeterValue,
   type OCPP16MeterValuesRequest,
   type OCPP16MeterValuesResponse,
   OCPP16RequestCommand,
@@ -86,9 +85,6 @@ export class OCPP16ResponseService extends OCPPResponseService {
   private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
 
   public constructor () {
-    // if (new.target.name === moduleName) {
-    //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
-    // }
     super(OCPPVersion.VERSION_16)
     this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
       [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
@@ -119,13 +115,13 @@ export class OCPP16ResponseService extends OCPPResponseService {
     ])
     this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
       OCPP16ServiceUtils.createResponsePayloadConfigs(),
-      OCPP16ServiceUtils.createResponseFactoryOptions(moduleName, 'constructor'),
+      OCPP16ServiceUtils.createResponsePayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.incomingRequestResponsePayloadValidateFunctions =
       OCPP16ServiceUtils.createPayloadValidatorMap(
         OCPP16ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
-        OCPP16ServiceUtils.createIncomingRequestResponseFactoryOptions(moduleName, 'constructor'),
+        OCPP16ServiceUtils.createIncomingRequestResponsePayloadOptions(moduleName, 'constructor'),
         this.ajvIncomingRequest
       )
     this.validatePayload = this.validatePayload.bind(this)
@@ -197,7 +193,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
           payload,
           undefined,
           2
-        )} while the charging station is not registered on the central server`,
+        )} while the charging station is not registered on the central system`,
         commandName,
         payload
       )
@@ -304,7 +300,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       }
       const logMsg = `${chargingStation.logPrefix()} ${moduleName}.handleResponseBootNotification: Charging station in '${
         payload.status
-      }' state on the central server`
+      }' state on the central system`
       payload.status === RegistrationStatusEnumType.REJECTED
         ? logger.warn(logMsg)
         : logger.info(logMsg)
@@ -557,7 +553,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
             chargingStation,
             transactionConnectorId,
             requestPayload.meterStop
-          ) as OCPP16MeterValue,
+          ),
         ],
         transactionId: requestPayload.transactionId,
       }))
index a97598d88f33638410e2e077c241c0e7ba811038..1f9f1a220fc2d9adba35d338197f2d325e6dde95 100644 (file)
@@ -467,23 +467,6 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
       methodName
     )
 
-  /**
-   * Factory options for OCPP 1.6 Incoming Request Response Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 incoming request response validators
-   */
-  public static createIncomingRequestResponseFactoryOptions = (
-    moduleName: string,
-    methodName: string
-  ) =>
-    OCPP16ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_16,
-      'assets/json-schemas/ocpp/1.6',
-      moduleName,
-      methodName
-    )
-
   /**
    * OCPP 1.6 Incoming Request Response Service validator configurations
    * @returns Array of validator configuration tuples
@@ -563,12 +546,15 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   ]
 
   /**
-   * Factory options for OCPP 1.6 Request Service
+   * Factory options for OCPP 1.6 Incoming Request Response Service
    * @param moduleName - Name of the OCPP module
    * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 validators
+   * @returns Factory options object for OCPP 1.6 incoming request response validators
    */
-  public static createRequestFactoryOptions = (moduleName: string, methodName: string) =>
+  public static createIncomingRequestResponsePayloadOptions = (
+    moduleName: string,
+    methodName: string
+  ) =>
     OCPP16ServiceUtils.PayloadValidatorOptions(
       OCPPVersion.VERSION_16,
       'assets/json-schemas/ocpp/1.6',
@@ -621,12 +607,12 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   ]
 
   /**
-   * Factory options for OCPP 1.6 Response Service
+   * Factory options for OCPP 1.6 Request Service
    * @param moduleName - Name of the OCPP module
    * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 response validators
+   * @returns Factory options object for OCPP 1.6 validators
    */
-  public static createResponseFactoryOptions = (moduleName: string, methodName: string) =>
+  public static createRequestPayloadOptions = (moduleName: string, methodName: string) =>
     OCPP16ServiceUtils.PayloadValidatorOptions(
       OCPPVersion.VERSION_16,
       'assets/json-schemas/ocpp/1.6',
@@ -684,6 +670,20 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
     ],
   ]
 
+  /**
+   * Factory options for OCPP 1.6 Response Service
+   * @param moduleName - Name of the OCPP module
+   * @param methodName - Name of the method/command
+   * @returns Factory options object for OCPP 1.6 response validators
+   */
+  public static createResponsePayloadOptions = (moduleName: string, methodName: string) =>
+    OCPP16ServiceUtils.PayloadValidatorOptions(
+      OCPPVersion.VERSION_16,
+      'assets/json-schemas/ocpp/1.6',
+      moduleName,
+      methodName
+    )
+
   public static hasReservation = (
     chargingStation: ChargingStation,
     connectorId: number,
index 4c4051e8e94218af2163c30643041a32718f69fd..4f7412b5e9d1d1b1723319a90b24b816269b12ce 100644 (file)
@@ -5,6 +5,7 @@ import type { ValidateFunction } from 'ajv'
 import type { ChargingStation } from '../../../charging-station/index.js'
 import type {
   OCPP20ChargingProfileType,
+  OCPP20ChargingScheduleType,
   OCPP20IdTokenType,
 } from '../../../types/ocpp/2.0/Transaction.js'
 
@@ -50,6 +51,12 @@ import {
   SetVariableStatusEnumType,
   StopTransactionReason,
 } from '../../../types/index.js'
+import {
+  OCPP20ChargingProfileKindEnumType,
+  OCPP20ChargingProfilePurposeEnumType,
+  OCPP20ChargingRateUnitEnumType,
+  OCPP20ReasonEnumType,
+} from '../../../types/ocpp/2.0/Transaction.js'
 import { StandardParametersKey } from '../../../types/ocpp/Configuration.js'
 import {
   convertToIntOrNaN,
@@ -59,7 +66,7 @@ import {
   validateUUID,
 } from '../../../utils/index.js'
 import { getConfigurationKey } from '../../ConfigurationKeyUtils.js'
-import { resetConnectorStatus } from '../../Helpers.js'
+import { getIdTagsFile, resetConnectorStatus } from '../../Helpers.js'
 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
 import { restoreConnectorStatus, sendAndSetConnectorStatus } from '../OCPPServiceUtils.js'
 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
@@ -108,7 +115,7 @@ const moduleName = 'OCPP20IncomingRequestService'
  * 4. Business logic executed with variable model integration
  * 5. Response payload validated and sent back to CSMS
  * @see {@link validatePayload} Request payload validation method
- * @see {@link handleRequestRequestStartTransaction} Example OCPP 2.0+ request handler
+ * @see {@link handleRequestStartTransaction} Example OCPP 2.0+ request handler
  * @see {@link OCPP20VariableManager} Variable management integration
  */
 
@@ -123,9 +130,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
   private readonly reportDataCache: Map<number, ReportDataType[]>
 
   public constructor () {
-    // if (new.target.name === moduleName) {
-    //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
-    // }
     super(OCPPVersion.VERSION_201)
     this.reportDataCache = new Map<number, ReportDataType[]>()
     this.incomingRequestHandlers = new Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>([
@@ -143,11 +147,11 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       ],
       [
         OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
-        this.handleRequestRequestStartTransaction.bind(this) as unknown as IncomingRequestHandler,
+        this.handleRequestStartTransaction.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
         OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION,
-        this.handleRequestRequestStopTransaction.bind(this) as unknown as IncomingRequestHandler,
+        this.handleRequestStopTransaction.bind(this) as unknown as IncomingRequestHandler,
       ],
       [
         OCPP20IncomingRequestCommand.RESET,
@@ -385,7 +389,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
           commandPayload,
           undefined,
           2
-        )} while the charging station is in pending state on the central server`,
+        )} while the charging station is in pending state on the CSMS`,
         commandName,
         commandPayload
       )
@@ -437,7 +441,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
           commandPayload,
           undefined,
           2
-        )} while the charging station is not registered on the central server`,
+        )} while the charging station is not registered on the CSMS`,
         commandName,
         commandPayload
       )
@@ -874,6 +878,223 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
   }
 
+  /**
+   * Handles OCPP 2.0 Reset request from central system with enhanced EVSE-specific support
+   * Initiates station or EVSE reset based on request parameters and transaction states
+   * @param chargingStation - The charging station instance processing the request
+   * @param commandPayload - Reset request payload with type and optional EVSE ID
+   * @returns Promise resolving to ResetResponse indicating operation status
+   */
+
+  private async handleRequestReset (
+    chargingStation: ChargingStation,
+    commandPayload: OCPP20ResetRequest
+  ): Promise<OCPP20ResetResponse> {
+    logger.debug(
+      `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Reset request received with type ${commandPayload.type}${commandPayload.evseId !== undefined ? ` for EVSE ${commandPayload.evseId.toString()}` : ''}`
+    )
+
+    const { evseId, type } = commandPayload
+
+    if (evseId !== undefined && evseId > 0) {
+      // Check if the charging station supports EVSE-specific reset
+      if (!chargingStation.hasEvses) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Charging station does not support EVSE-specific reset`
+        )
+        return {
+          status: ResetStatusEnumType.Rejected,
+          statusInfo: {
+            additionalInfo: 'Charging station does not support resetting individual EVSE',
+            reasonCode: ReasonCodeEnumType.UnsupportedRequest,
+          },
+        }
+      }
+
+      // Check if the EVSE exists
+      const evseExists = chargingStation.evses.has(evseId)
+      if (!evseExists) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: EVSE ${evseId.toString()} not found, rejecting reset request`
+        )
+        return {
+          status: ResetStatusEnumType.Rejected,
+          statusInfo: {
+            additionalInfo: `EVSE ${evseId.toString()} does not exist on charging station`,
+            reasonCode: ReasonCodeEnumType.UnknownEvse,
+          },
+        }
+      }
+    }
+
+    // Check for active transactions
+    const hasActiveTransactions = chargingStation.getNumberOfRunningTransactions() > 0
+
+    // Check for EVSE-specific active transactions if evseId is provided
+    let hasEvseActiveTransactions = false
+    if (evseId !== undefined && evseId > 0) {
+      // Check if there are active transactions on the specific EVSE
+      const evse = chargingStation.evses.get(evseId)
+      if (evse) {
+        for (const [, connector] of evse.connectors) {
+          if (connector.transactionId !== undefined) {
+            hasEvseActiveTransactions = true
+            break
+          }
+        }
+      }
+    }
+
+    try {
+      if (type === ResetEnumType.Immediate) {
+        if (evseId !== undefined) {
+          // EVSE-specific immediate reset
+          if (hasEvseActiveTransactions) {
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate EVSE reset with active transaction, will terminate transaction and reset EVSE ${evseId.toString()}`
+            )
+
+            // Implement EVSE-specific transaction termination
+            await this.terminateEvseTransactions(
+              chargingStation,
+              evseId,
+              OCPP20ReasonEnumType.ImmediateReset
+            )
+            this.scheduleEvseReset(chargingStation, evseId, true)
+
+            return {
+              status: ResetStatusEnumType.Accepted,
+            }
+          } else {
+            // Reset EVSE immediately
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate EVSE reset without active transactions for EVSE ${evseId.toString()}`
+            )
+
+            this.scheduleEvseReset(chargingStation, evseId, false)
+
+            return {
+              status: ResetStatusEnumType.Accepted,
+            }
+          }
+        } else {
+          // Charging station immediate reset
+          if (hasActiveTransactions) {
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate reset with active transactions, will terminate transactions and reset`
+            )
+
+            // Implement proper transaction termination with TransactionEventRequest
+            await this.terminateAllTransactions(
+              chargingStation,
+              OCPP20ReasonEnumType.ImmediateReset
+            )
+            chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
+              logger.error(
+                `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error during immediate reset:`,
+                error
+              )
+            })
+
+            return {
+              status: ResetStatusEnumType.Accepted,
+            }
+          } else {
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate reset without active transactions`
+            )
+
+            // Send StatusNotification(Unavailable) for all connectors
+            this.sendAllConnectorsStatusNotifications(
+              chargingStation,
+              OCPP20ConnectorStatusEnumType.Unavailable
+            )
+            chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
+              logger.error(
+                `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error during immediate reset:`,
+                error
+              )
+            })
+
+            return {
+              status: ResetStatusEnumType.Accepted,
+            }
+          }
+        }
+      } else {
+        // OnIdle reset
+        if (evseId !== undefined) {
+          // EVSE-specific OnIdle reset
+          if (hasEvseActiveTransactions) {
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle EVSE reset scheduled for EVSE ${evseId.toString()}, waiting for transaction completion`
+            )
+
+            // Monitor EVSE for transaction completion and schedule reset when idle
+            this.scheduleEvseResetOnIdle(chargingStation, evseId)
+
+            return {
+              status: ResetStatusEnumType.Scheduled,
+            }
+          } else {
+            // No active transactions on EVSE, reset immediately
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle EVSE reset without active transactions for EVSE ${evseId.toString()}`
+            )
+
+            this.scheduleEvseReset(chargingStation, evseId, false)
+
+            return {
+              status: ResetStatusEnumType.Accepted,
+            }
+          }
+        } else {
+          // Charging station OnIdle reset
+          if (hasActiveTransactions) {
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle reset scheduled, waiting for transaction completion`
+            )
+
+            this.scheduleResetOnIdle(chargingStation)
+
+            return {
+              status: ResetStatusEnumType.Scheduled,
+            }
+          } else {
+            // No active transactions, reset immediately
+            logger.info(
+              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle reset without active transactions, resetting immediately`
+            )
+
+            chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
+              logger.error(
+                `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error during OnIdle reset:`,
+                error
+              )
+            })
+
+            return {
+              status: ResetStatusEnumType.Accepted,
+            }
+          }
+        }
+      }
+    } catch (error) {
+      logger.error(
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error handling reset request:`,
+        error
+      )
+
+      return {
+        status: ResetStatusEnumType.Rejected,
+        statusInfo: {
+          additionalInfo: 'Internal error occurred while processing reset request',
+          reasonCode: ReasonCodeEnumType.InternalError,
+        },
+      }
+    }
+  }
+
   /**
    * Handles OCPP 2.0 RequestStartTransaction request from central system
    * Initiates charging transaction on specified EVSE with enhanced authorization
@@ -881,20 +1102,20 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
    * @param commandPayload - RequestStartTransaction request payload with EVSE, ID token and profiles
    * @returns Promise resolving to RequestStartTransactionResponse with status and transaction details
    */
-  private async handleRequestRequestStartTransaction (
+  private async handleRequestStartTransaction (
     chargingStation: ChargingStation,
     commandPayload: OCPP20RequestStartTransactionRequest
   ): Promise<OCPP20RequestStartTransactionResponse> {
     const { chargingProfile, evseId, groupIdToken, idToken, remoteStartId } = commandPayload
     logger.info(
-      `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Remote start transaction request received on EVSE ${evseId?.toString() ?? 'undefined'} with idToken ${idToken.idToken} and remoteStartId ${remoteStartId.toString()}`
+      `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Remote start transaction request received on EVSE ${evseId?.toString() ?? 'undefined'} with idToken ${idToken.idToken} and remoteStartId ${remoteStartId.toString()}`
     )
 
     // Validate that EVSE ID is provided
     if (evseId == null) {
       const errorMsg = 'EVSE ID is required for RequestStartTransaction'
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: ${errorMsg}`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: ${errorMsg}`
       )
       throw new OCPPError(
         ErrorType.PROPERTY_CONSTRAINT_VIOLATION,
@@ -909,7 +1130,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     if (evse == null) {
       const errorMsg = `EVSE ${evseId.toString()} does not exist on charging station`
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: ${errorMsg}`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: ${errorMsg}`
       )
       throw new OCPPError(
         ErrorType.PROPERTY_CONSTRAINT_VIOLATION,
@@ -925,7 +1146,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     if (connectorStatus == null || connectorId == null) {
       const errorMsg = `Connector ${connectorId?.toString() ?? 'undefined'} status is undefined`
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: ${errorMsg}`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: ${errorMsg}`
       )
       throw new OCPPError(
         ErrorType.INTERNAL_ERROR,
@@ -938,7 +1159,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     // Check if connector is available for a new transaction
     if (connectorStatus.transactionStarted === true) {
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Connector ${connectorId.toString()} already has an active transaction`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Connector ${connectorId.toString()} already has an active transaction`
       )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
@@ -949,10 +1170,10 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     // Authorize idToken
     let isAuthorized = false
     try {
-      isAuthorized = await this.isIdTokenAuthorized(chargingStation, idToken)
+      isAuthorized = this.isIdTokenAuthorized(chargingStation, idToken)
     } catch (error) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Authorization error for ${idToken.idToken}:`,
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Authorization error for ${idToken.idToken}:`,
         error
       )
       return {
@@ -963,7 +1184,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
 
     if (!isAuthorized) {
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: IdToken ${idToken.idToken} is not authorized`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: IdToken ${idToken.idToken} is not authorized`
       )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
@@ -975,10 +1196,10 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     if (groupIdToken != null) {
       let isGroupAuthorized = false
       try {
-        isGroupAuthorized = await this.isIdTokenAuthorized(chargingStation, groupIdToken)
+        isGroupAuthorized = this.isIdTokenAuthorized(chargingStation, groupIdToken)
       } catch (error) {
         logger.error(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Group authorization error for ${groupIdToken.idToken}:`,
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Group authorization error for ${groupIdToken.idToken}:`,
           error
         )
         return {
@@ -989,7 +1210,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
 
       if (!isGroupAuthorized) {
         logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: GroupIdToken ${groupIdToken.idToken} is not authorized`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: GroupIdToken ${groupIdToken.idToken} is not authorized`
         )
         return {
           status: RequestStartStopStatusEnumType.Rejected,
@@ -1005,7 +1226,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         isValidProfile = this.validateChargingProfile(chargingStation, chargingProfile, evseId)
       } catch (error) {
         logger.error(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Charging profile validation error:`,
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Charging profile validation error:`,
           error
         )
         return {
@@ -1016,7 +1237,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
 
       if (!isValidProfile) {
         logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Invalid charging profile`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Invalid charging profile`
         )
         return {
           status: RequestStartStopStatusEnumType.Rejected,
@@ -1030,7 +1251,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     try {
       // Set connector transaction state
       logger.debug(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Setting transaction state for connector ${connectorId.toString()}, transaction ID: ${transactionId}`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Setting transaction state for connector ${connectorId.toString()}, transaction ID: ${transactionId}`
       )
       connectorStatus.transactionStarted = true
       connectorStatus.transactionId = transactionId
@@ -1039,12 +1260,12 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       connectorStatus.transactionEnergyActiveImportRegisterValue = 0
       connectorStatus.remoteStartId = remoteStartId
       logger.debug(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Transaction state set successfully for connector ${connectorId.toString()}`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Transaction state set successfully for connector ${connectorId.toString()}`
       )
 
       // Update connector status to Occupied
       logger.debug(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Updating connector ${connectorId.toString()} status to Occupied`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Updating connector ${connectorId.toString()} status to Occupied`
       )
       await sendAndSetConnectorStatus(
         chargingStation,
@@ -1057,14 +1278,13 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       if (chargingProfile != null) {
         connectorStatus.chargingProfiles ??= []
         connectorStatus.chargingProfiles.push(chargingProfile)
-        // TODO: Implement charging profile storage
         logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Charging profile stored for transaction ${transactionId} (TODO: implement profile storage)`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Charging profile stored for transaction ${transactionId}`
         )
       }
 
       logger.info(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Remote start transaction ACCEPTED on #${connectorId.toString()} for idToken '${idToken.idToken}'`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Remote start transaction ACCEPTED on #${connectorId.toString()} for idToken '${idToken.idToken}'`
       )
 
       return {
@@ -1074,7 +1294,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     } catch (error) {
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId, evseId)
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStartTransaction: Error starting transaction:`,
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Error starting transaction:`,
         error
       )
       return {
@@ -1084,19 +1304,19 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
   }
 
-  private async handleRequestRequestStopTransaction (
+  private async handleRequestStopTransaction (
     chargingStation: ChargingStation,
     commandPayload: OCPP20RequestStopTransactionRequest
   ): Promise<OCPP20RequestStopTransactionResponse> {
     const { transactionId } = commandPayload
     logger.info(
-      `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Remote stop transaction request received for transaction ID ${transactionId}`
+      `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Remote stop transaction request received for transaction ID ${transactionId}`
     )
 
     if (!validateUUID(transactionId)) {
       logger.warn(
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Invalid transaction ID format (expected UUID): ${transactionId}`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Invalid transaction ID format (expected UUID): ${transactionId}`
       )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
@@ -1106,7 +1326,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     const evseId = chargingStation.getEvseIdByTransactionId(transactionId)
     if (evseId == null) {
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Transaction ID ${transactionId} does not exist on any EVSE`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Transaction ID ${transactionId} does not exist on any EVSE`
       )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
@@ -1116,7 +1336,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)
     if (connectorId == null) {
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Transaction ID ${transactionId} does not exist on any connector`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Transaction ID ${transactionId} does not exist on any connector`
       )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
@@ -1132,7 +1352,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
 
       if (stopResponse.status === GenericStatus.Accepted) {
         logger.info(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
+          `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
         )
         return {
           status: RequestStartStopStatusEnumType.Accepted,
@@ -1140,14 +1360,14 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
       }
 
       logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Remote stop transaction REJECTED for transactionId '${transactionId}'`
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Remote stop transaction REJECTED for transactionId '${transactionId}'`
       )
       return {
         status: RequestStartStopStatusEnumType.Rejected,
       }
     } catch (error) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestRequestStopTransaction: Error occurred during remote stop transaction for transaction ID ${transactionId} on connector ${connectorId.toString()}:`,
+        `${chargingStation.logPrefix()} ${moduleName}.handleRequestStopTransaction: Error occurred during remote stop transaction for transaction ID ${transactionId} on connector ${connectorId.toString()}:`,
         error
       )
       return {
@@ -1156,223 +1376,104 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }
   }
 
-  private handleRequestReset (
+  // Helper methods for RequestStartTransaction
+  private isIdTokenAuthorized (
     chargingStation: ChargingStation,
-    commandPayload: OCPP20ResetRequest
-  ): OCPP20ResetResponse {
+    idToken: OCPP20IdTokenType
+  ): boolean {
+    /**
+     * OCPP 2.0 Authorization Logic Implementation
+     *
+     * OCPP 2.0 handles authorization differently from 1.6:
+     * 1. Check if authorization is required (LocalAuthorizeOffline, AuthorizeRemoteStart variables)
+     * 2. Local authorization list validation if enabled
+     * 3. For OCPP 2.0, there's no explicit AuthorizeRequest - authorization is validated
+     *    through configuration variables and local auth lists
+     * 4. Remote validation through TransactionEvent if needed
+     */
+
     logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Reset request received with type ${commandPayload.type}${commandPayload.evseId !== undefined ? ` for EVSE ${commandPayload.evseId.toString()}` : ''}`
+      `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Validating idToken ${idToken.idToken} of type ${idToken.type}`
     )
 
-    const { evseId, type } = commandPayload
+    try {
+      // Check if local authorization is disabled and remote authorization is also disabled
+      const localAuthListEnabled = chargingStation.getLocalAuthListEnabled()
+      const remoteAuthorizationEnabled = chargingStation.stationInfo?.remoteAuthorization ?? true
 
-    if (evseId !== undefined && evseId > 0) {
-      // Check if the charging station supports EVSE-specific reset
-      if (!chargingStation.hasEvses) {
+      if (!localAuthListEnabled && !remoteAuthorizationEnabled) {
         logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Charging station does not support EVSE-specific reset`
+          `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Both local and remote authorization are disabled. Allowing access but this may indicate misconfiguration.`
         )
-        return {
-          status: ResetStatusEnumType.Rejected,
-          statusInfo: {
-            additionalInfo: 'Charging station does not support resetting individual EVSE',
-            reasonCode: ReasonCodeEnumType.UnsupportedRequest,
-          },
-        }
+        return true
       }
 
-      // Check if the EVSE exists
-      const evseExists = chargingStation.evses.has(evseId)
-      if (!evseExists) {
-        logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: EVSE ${evseId.toString()} not found, rejecting reset request`
-        )
-        return {
-          status: ResetStatusEnumType.Rejected,
-          statusInfo: {
-            additionalInfo: `EVSE ${evseId.toString()} does not exist on charging station`,
-            reasonCode: ReasonCodeEnumType.UnknownEvse,
-          },
+      // 1. Check local authorization list first (if enabled)
+      if (localAuthListEnabled) {
+        const isLocalAuthorized = this.isIdTokenLocalAuthorized(chargingStation, idToken.idToken)
+        if (isLocalAuthorized) {
+          logger.debug(
+            `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${idToken.idToken} authorized via local auth list`
+          )
+          return true
         }
+        logger.debug(
+          `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${idToken.idToken} not found in local auth list`
+        )
       }
-    }
-
-    // Check for active transactions
-    const hasActiveTransactions = chargingStation.getNumberOfRunningTransactions() > 0
 
-    // Check for EVSE-specific active transactions if evseId is provided
-    let hasEvseActiveTransactions = false
-    if (evseId !== undefined && evseId > 0) {
-      // Check if there are active transactions on the specific EVSE
-      const evse = chargingStation.evses.get(evseId)
-      if (evse) {
-        for (const [, connector] of evse.connectors) {
-          if (connector.transactionId !== undefined) {
-            hasEvseActiveTransactions = true
-            break
-          }
-        }
+      // 2. For OCPP 2.0, if we can't authorize locally and remote auth is enabled,
+      //    we should validate through TransactionEvent mechanism or return false
+      //    In OCPP 2.0, there's no explicit remote authorize - it's handled during transaction events
+      if (remoteAuthorizationEnabled) {
+        logger.debug(
+          `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Remote authorization enabled but no explicit remote auth mechanism in OCPP 2.0 - deferring to transaction event validation`
+        )
+        // In OCPP 2.0, remote authorization happens during TransactionEvent processing
+        // For now, we'll allow the transaction to proceed and let the CSMS validate during TransactionEvent
+        return true
       }
-    }
 
-    try {
-      if (type === ResetEnumType.Immediate) {
-        if (evseId !== undefined) {
-          // EVSE-specific immediate reset
-          if (hasEvseActiveTransactions) {
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate EVSE reset with active transaction, will terminate transaction and reset EVSE ${evseId.toString()}`
-            )
-
-            // TODO: Implement EVSE-specific transaction termination
-            // For now, accept and schedule the reset
-            this.scheduleEvseReset(chargingStation, evseId, true)
-
-            return {
-              status: ResetStatusEnumType.Accepted,
-            }
-          } else {
-            // Reset EVSE immediately
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate EVSE reset without active transactions for EVSE ${evseId.toString()}`
-            )
-
-            this.scheduleEvseReset(chargingStation, evseId, false)
-
-            return {
-              status: ResetStatusEnumType.Accepted,
-            }
-          }
-        } else {
-          // Charging station immediate reset
-          if (hasActiveTransactions) {
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate reset with active transactions, will terminate transactions and reset`
-            )
-
-            // TODO: Implement proper transaction termination with TransactionEventRequest
-            // For now, reset immediately and let the reset handle transaction cleanup
-            chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
-              logger.error(
-                `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error during immediate reset:`,
-                error
-              )
-            })
-
-            return {
-              status: ResetStatusEnumType.Accepted,
-            }
-          } else {
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Immediate reset without active transactions`
-            )
-
-            // TODO: Send StatusNotification(Unavailable) for all connectors
-            chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
-              logger.error(
-                `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error during immediate reset:`,
-                error
-              )
-            })
-
-            return {
-              status: ResetStatusEnumType.Accepted,
-            }
-          }
-        }
-      } else {
-        // OnIdle reset
-        if (evseId !== undefined) {
-          // EVSE-specific OnIdle reset
-          if (hasEvseActiveTransactions) {
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle EVSE reset scheduled for EVSE ${evseId.toString()}, waiting for transaction completion`
-            )
-
-            // TODO: Implement proper monitoring of EVSE transaction completion
-            this.scheduleEvseResetOnIdle(chargingStation, evseId)
-
-            return {
-              status: ResetStatusEnumType.Scheduled,
-            }
-          } else {
-            // No active transactions on EVSE, reset immediately
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle EVSE reset without active transactions for EVSE ${evseId.toString()}`
-            )
-
-            this.scheduleEvseReset(chargingStation, evseId, false)
-
-            return {
-              status: ResetStatusEnumType.Accepted,
-            }
-          }
-        } else {
-          // Charging station OnIdle reset
-          if (hasActiveTransactions) {
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle reset scheduled, waiting for transaction completion`
-            )
-
-            this.scheduleResetOnIdle(chargingStation)
-
-            return {
-              status: ResetStatusEnumType.Scheduled,
-            }
-          } else {
-            // No active transactions, reset immediately
-            logger.info(
-              `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: OnIdle reset without active transactions, resetting immediately`
-            )
-
-            chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
-              logger.error(
-                `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error during OnIdle reset:`,
-                error
-              )
-            })
-
-            return {
-              status: ResetStatusEnumType.Accepted,
-            }
-          }
-        }
-      }
+      // 3. If we reach here, authorization failed
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${idToken.idToken} authorization failed - not found in local list and remote auth not configured`
+      )
+      return false
     } catch (error) {
       logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.handleRequestReset: Error handling reset request:`,
+        `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Error during authorization validation for ${idToken.idToken}:`,
         error
       )
-
-      return {
-        status: ResetStatusEnumType.Rejected,
-        statusInfo: {
-          additionalInfo: 'Internal error occurred while processing reset request',
-          reasonCode: ReasonCodeEnumType.InternalError,
-        },
-      }
+      // Fail securely - deny access on authorization errors
+      return false
     }
   }
 
-  // Helper methods for RequestStartTransaction
-  private async isIdTokenAuthorized (
+  /**
+   * Check if idToken is authorized in local authorization list
+   * @param chargingStation - The charging station instance
+   * @param idTokenString - The ID token string to validate
+   * @returns true if authorized locally, false otherwise
+   */
+  private isIdTokenLocalAuthorized (
     chargingStation: ChargingStation,
-    idToken: OCPP20IdTokenType
-  ): Promise<boolean> {
-    // TODO: Implement proper authorization logic
-    // This should check:
-    // 1. Local authorization list if enabled
-    // 2. Remote authorization via AuthorizeRequest if needed
-    // 3. Cache for known tokens
-    // 4. Return false if authorization fails
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Validating idToken ${idToken.idToken} of type ${idToken.type}`
-    )
-
-    // For now, return true to allow development/testing
-    // TODO: Implement actual async authorization logic
-    return await Promise.resolve(true)
+    idTokenString: string
+  ): boolean {
+    try {
+      return (
+        chargingStation.hasIdTags() &&
+        chargingStation.idTagsCache
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          .getIdTags(getIdTagsFile(chargingStation.stationInfo!)!)
+          ?.includes(idTokenString) === true
+      )
+    } catch (error) {
+      logger.error(
+        `${chargingStation.logPrefix()} ${moduleName}.isIdTokenLocalAuthorized: Error checking local authorization for ${idTokenString}:`,
+        error
+      )
+      return false
+    }
   }
 
   /**
@@ -1392,39 +1493,61 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     await restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
   }
 
+  /**
+   * Schedules EVSE reset with optional transaction termination
+   * @param chargingStation - The charging station instance
+   * @param evseId - The EVSE identifier to reset
+   * @param hasActiveTransactions - Whether there are active transactions to handle
+   */
   private scheduleEvseReset (
     chargingStation: ChargingStation,
     evseId: number,
-    terminateTransactions: boolean
+    hasActiveTransactions: boolean
   ): void {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseReset: Scheduling EVSE ${evseId.toString()} reset${terminateTransactions ? ' with transaction termination' : ''}`
+    // Send status notification for unavailable EVSE
+    this.sendEvseStatusNotifications(
+      chargingStation,
+      evseId,
+      OCPP20ConnectorStatusEnumType.Unavailable
     )
 
-    setTimeout(
-      () => {
-        // TODO: Implement actual EVSE-specific reset logic
-        // This should:
-        // 1. Send StatusNotification(Unavailable) for EVSE connectors (B11.FR.08)
-        // 2. Terminate active transactions if needed
-        // 3. Reset EVSE state
-        // 4. Restore EVSE to appropriate state after reset
-
-        logger.info(
-          `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseReset: EVSE ${evseId.toString()} reset executed`
-        )
-      },
-      terminateTransactions ? 1000 : 100
-    ) // Small delay for immediate execution
+    // Schedule the actual EVSE reset
+    setImmediate(() => {
+      logger.info(
+        `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseReset: Executing EVSE ${evseId.toString()} reset${hasActiveTransactions ? ' after transaction termination' : ''}`
+      )
+      // Reset EVSE - this would typically involve resetting the EVSE hardware/software
+      // For now, we'll restore connectors to available status after a short delay
+      setTimeout(() => {
+        const evse = chargingStation.evses.get(evseId)
+        if (evse) {
+          for (const [connectorId] of evse.connectors) {
+            const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+            restoreConnectorStatus(chargingStation, connectorId, connectorStatus).catch(
+              (error: unknown) => {
+                logger.error(
+                  `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseReset: Error restoring connector ${connectorId.toString()} status:`,
+                  error
+                )
+              }
+            )
+          }
+          logger.info(
+            `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseReset: EVSE ${evseId.toString()} reset completed`
+          )
+        }
+      }, 1000)
+    })
   }
 
+  /**
+   * Schedules EVSE reset on idle (when no active transactions)
+   * @param chargingStation - The charging station instance
+   * @param evseId - The EVSE identifier to reset
+   */
   private scheduleEvseResetOnIdle (chargingStation: ChargingStation, evseId: number): void {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseResetOnIdle: Monitoring EVSE ${evseId.toString()} for transaction completion`
-    )
-
-    // TODO: Implement proper monitoring logic
-    const checkInterval = setInterval(() => {
+    // Monitor for transaction completion and reset when idle
+    const monitorInterval = setInterval(() => {
       const evse = chargingStation.evses.get(evseId)
       if (evse) {
         let hasActiveTransactions = false
@@ -1436,25 +1559,32 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         }
 
         if (!hasActiveTransactions) {
-          clearInterval(checkInterval)
+          clearInterval(monitorInterval)
+          logger.info(
+            `${chargingStation.logPrefix()} ${moduleName}.scheduleEvseResetOnIdle: EVSE ${evseId.toString()} is now idle, executing reset`
+          )
           this.scheduleEvseReset(chargingStation, evseId, false)
         }
+      } else {
+        clearInterval(monitorInterval)
       }
     }, 5000) // Check every 5 seconds
   }
 
+  /**
+   * Schedules charging station reset on idle (when no active transactions)
+   * @param chargingStation - The charging station instance
+   */
   private scheduleResetOnIdle (chargingStation: ChargingStation): void {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.scheduleResetOnIdle: Monitoring charging station for transaction completion`
-    )
-
-    // TODO: Implement proper monitoring logic
-    const checkInterval = setInterval(() => {
+    // Monitor for transaction completion and reset when idle
+    const monitorInterval = setInterval(() => {
       const hasActiveTransactions = chargingStation.getNumberOfRunningTransactions() > 0
 
       if (!hasActiveTransactions) {
-        clearInterval(checkInterval)
-        // TODO: Use OCPP2 stop transaction reason when implemented
+        clearInterval(monitorInterval)
+        logger.info(
+          `${chargingStation.logPrefix()} ${moduleName}.scheduleResetOnIdle: Charging station is now idle, executing reset`
+        )
         chargingStation.reset(StopTransactionReason.REMOTE).catch((error: unknown) => {
           logger.error(
             `${chargingStation.logPrefix()} ${moduleName}.scheduleResetOnIdle: Error during scheduled reset:`,
@@ -1465,6 +1595,59 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     }, 5000) // Check every 5 seconds
   }
 
+  /**
+   * Sends status notifications for all connectors on the charging station
+   * @param chargingStation - The charging station instance
+   * @param status - The connector status to send
+   */
+  private sendAllConnectorsStatusNotifications (
+    chargingStation: ChargingStation,
+    status: OCPP20ConnectorStatusEnumType
+  ): void {
+    for (const [, evse] of chargingStation.evses) {
+      for (const [connectorId] of evse.connectors) {
+        sendAndSetConnectorStatus(
+          chargingStation,
+          connectorId,
+          status as ConnectorStatusEnum
+        ).catch((error: unknown) => {
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.sendAllConnectorsStatusNotifications: Error sending status notification for connector ${connectorId.toString()}:`,
+            error
+          )
+        })
+      }
+    }
+  }
+
+  /**
+   * Sends status notifications for all connectors on the specified EVSE
+   * @param chargingStation - The charging station instance
+   * @param evseId - The EVSE identifier
+   * @param status - The connector status to send
+   */
+  private sendEvseStatusNotifications (
+    chargingStation: ChargingStation,
+    evseId: number,
+    status: OCPP20ConnectorStatusEnumType
+  ): void {
+    const evse = chargingStation.evses.get(evseId)
+    if (evse) {
+      for (const [connectorId] of evse.connectors) {
+        sendAndSetConnectorStatus(
+          chargingStation,
+          connectorId,
+          status as ConnectorStatusEnum
+        ).catch((error: unknown) => {
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.sendEvseStatusNotifications: Error sending status notification for connector ${connectorId.toString()}:`,
+            error
+          )
+        })
+      }
+    }
+  }
+
   private async sendNotifyReportRequest (
     chargingStation: ChargingStation,
     request: OCPP20GetBaseReportRequest,
@@ -1520,23 +1703,430 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     this.reportDataCache.delete(requestId)
   }
 
+  /**
+   * Terminates all active transactions on the charging station using OCPP 2.0 TransactionEventRequest
+   * @param chargingStation - The charging station instance
+   * @param reason - The reason for transaction termination
+   */
+  private async terminateAllTransactions (
+    chargingStation: ChargingStation,
+    reason: OCPP20ReasonEnumType
+  ): Promise<void> {
+    const terminationPromises: Promise<unknown>[] = []
+
+    for (const [evseId, evse] of chargingStation.evses) {
+      for (const [connectorId, connector] of evse.connectors) {
+        if (connector.transactionId !== undefined) {
+          logger.info(
+            `${chargingStation.logPrefix()} ${moduleName}.terminateAllTransactions: Terminating transaction ${connector.transactionId.toString()} on connector ${connectorId.toString()}`
+          )
+          // Use the proper OCPP 2.0 transaction termination method
+          terminationPromises.push(
+            OCPP20ServiceUtils.requestStopTransaction(chargingStation, connectorId, evseId).catch(
+              (error: unknown) => {
+                logger.error(
+                  `${chargingStation.logPrefix()} ${moduleName}.terminateAllTransactions: Error terminating transaction on connector ${connectorId.toString()}:`,
+                  error
+                )
+              }
+            )
+          )
+        }
+      }
+    }
+
+    if (terminationPromises.length > 0) {
+      await Promise.all(terminationPromises)
+      logger.info(
+        `${chargingStation.logPrefix()} ${moduleName}.terminateAllTransactions: All transactions terminated on charging station`
+      )
+    }
+  }
+
+  /**
+   * Terminates all active transactions on the specified EVSE using OCPP 2.0 TransactionEventRequest
+   * @param chargingStation - The charging station instance
+   * @param evseId - The EVSE identifier to terminate transactions on
+   * @param reason - The reason for transaction termination
+   */
+  private async terminateEvseTransactions (
+    chargingStation: ChargingStation,
+    evseId: number,
+    reason: OCPP20ReasonEnumType
+  ): Promise<void> {
+    const evse = chargingStation.evses.get(evseId)
+    if (!evse) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.terminateEvseTransactions: EVSE ${evseId.toString()} not found`
+      )
+      return
+    }
+
+    const terminationPromises: Promise<unknown>[] = []
+    for (const [connectorId, connector] of evse.connectors) {
+      if (connector.transactionId !== undefined) {
+        logger.info(
+          `${chargingStation.logPrefix()} ${moduleName}.terminateEvseTransactions: Terminating transaction ${connector.transactionId.toString()} on connector ${connectorId.toString()}`
+        )
+        // Use the proper OCPP 2.0 transaction termination method
+        terminationPromises.push(
+          OCPP20ServiceUtils.requestStopTransaction(chargingStation, connectorId, evseId).catch(
+            (error: unknown) => {
+              logger.error(
+                `${chargingStation.logPrefix()} ${moduleName}.terminateEvseTransactions: Error terminating transaction on connector ${connectorId.toString()}:`,
+                error
+              )
+            }
+          )
+        )
+      }
+    }
+
+    if (terminationPromises.length > 0) {
+      await Promise.all(terminationPromises)
+      logger.info(
+        `${chargingStation.logPrefix()} ${moduleName}.terminateEvseTransactions: All transactions terminated on EVSE ${evseId.toString()}`
+      )
+    }
+  }
+
   private validateChargingProfile (
     chargingStation: ChargingStation,
     chargingProfile: OCPP20ChargingProfileType,
     evseId: number
   ): boolean {
-    // TODO: Implement proper charging profile validation
-    // This should validate:
-    // 1. Profile structure and required fields
-    // 2. Schedule periods and limits
-    // 3. Compatibility with EVSE capabilities
-    // 4. Time constraints and validity
-
     logger.debug(
       `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Validating charging profile ${chargingProfile.id.toString()} for EVSE ${evseId.toString()}`
     )
 
-    // For now, return true to allow development/testing
+    // Basic validation - check required fields
+    if (!chargingProfile.id || !chargingProfile.stackLevel) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Invalid charging profile - missing required fields`
+      )
+      return false
+    }
+
+    // Validate stack level range (OCPP 2.0 spec: 0-9)
+    if (chargingProfile.stackLevel < 0 || chargingProfile.stackLevel > 9) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Invalid stack level ${chargingProfile.stackLevel.toString()}, must be 0-9`
+      )
+      return false
+    }
+
+    // Validate charging profile ID is positive
+    if (chargingProfile.id <= 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Invalid charging profile ID ${chargingProfile.id.toString()}, must be positive`
+      )
+      return false
+    }
+
+    // Validate EVSE compatibility
+    if (!chargingStation.hasEvses && evseId > 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: EVSE ${evseId.toString()} not supported by this charging station`
+      )
+      return false
+    }
+
+    if (chargingStation.hasEvses && evseId > chargingStation.getNumberOfEvses()) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: EVSE ${evseId.toString()} exceeds available EVSEs (${chargingStation.getNumberOfEvses().toString()})`
+      )
+      return false
+    }
+
+    // Validate charging schedules array is not empty
+    if (chargingProfile.chargingSchedule.length === 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Charging profile must contain at least one charging schedule`
+      )
+      return false
+    }
+
+    // Time constraints validation
+    const now = new Date()
+    if (chargingProfile.validFrom && chargingProfile.validTo) {
+      if (chargingProfile.validFrom >= chargingProfile.validTo) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: validFrom must be before validTo`
+        )
+        return false
+      }
+    }
+
+    if (chargingProfile.validTo && chargingProfile.validTo <= now) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Charging profile already expired`
+      )
+      return false
+    }
+
+    // Validate recurrency kind compatibility with profile kind
+    if (
+      chargingProfile.recurrencyKind &&
+      chargingProfile.chargingProfileKind !== OCPP20ChargingProfileKindEnumType.Recurring
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: recurrencyKind only valid for Recurring profile kind`
+      )
+      return false
+    }
+
+    if (
+      chargingProfile.chargingProfileKind === OCPP20ChargingProfileKindEnumType.Recurring &&
+      !chargingProfile.recurrencyKind
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Recurring profile kind requires recurrencyKind`
+      )
+      return false
+    }
+
+    // Validate each charging schedule
+    for (const [scheduleIndex, schedule] of chargingProfile.chargingSchedule.entries()) {
+      if (
+        !this.validateChargingSchedule(
+          chargingStation,
+          schedule,
+          scheduleIndex,
+          chargingProfile,
+          evseId
+        )
+      ) {
+        return false
+      }
+    }
+
+    // Profile purpose specific validations
+    if (!this.validateChargingProfilePurpose(chargingStation, chargingProfile, evseId)) {
+      return false
+    }
+
+    logger.debug(
+      `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Charging profile ${chargingProfile.id.toString()} validation passed`
+    )
+    return true
+  }
+
+  /**
+   * Validates charging profile purpose-specific business rules
+   * @param chargingStation - The charging station instance
+   * @param chargingProfile - The charging profile to validate
+   * @param evseId - EVSE identifier
+   * @returns True if purpose validation passes, false otherwise
+   */
+  private validateChargingProfilePurpose (
+    chargingStation: ChargingStation,
+    chargingProfile: OCPP20ChargingProfileType,
+    evseId: number
+  ): boolean {
+    logger.debug(
+      `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: Validating purpose-specific rules for profile ${chargingProfile.id.toString()} with purpose ${chargingProfile.chargingProfilePurpose}`
+    )
+
+    switch (chargingProfile.chargingProfilePurpose) {
+      case OCPP20ChargingProfilePurposeEnumType.ChargingStationExternalConstraints:
+        // ChargingStationExternalConstraints must apply to EVSE 0 (entire station)
+        if (evseId !== 0) {
+          logger.warn(
+            `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: ChargingStationExternalConstraints must apply to EVSE 0, got EVSE ${evseId.toString()}`
+          )
+          return false
+        }
+        break
+
+      case OCPP20ChargingProfilePurposeEnumType.ChargingStationMaxProfile:
+        // ChargingStationMaxProfile must apply to EVSE 0 (entire station)
+        if (evseId !== 0) {
+          logger.warn(
+            `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: ChargingStationMaxProfile must apply to EVSE 0, got EVSE ${evseId.toString()}`
+          )
+          return false
+        }
+        break
+
+      case OCPP20ChargingProfilePurposeEnumType.TxDefaultProfile:
+        // TxDefaultProfile can apply to EVSE 0 or specific EVSE
+        // No additional constraints beyond general EVSE validation
+        break
+
+      case OCPP20ChargingProfilePurposeEnumType.TxProfile:
+        // TxProfile must apply to a specific EVSE (not 0)
+        if (evseId === 0) {
+          logger.warn(
+            `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: TxProfile cannot apply to EVSE 0, must target specific EVSE`
+          )
+          return false
+        }
+
+        // TxProfile should have a transactionId when used with active transaction
+        if (!chargingProfile.transactionId) {
+          logger.debug(
+            `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: TxProfile without transactionId - may be for future use`
+          )
+        }
+        break
+
+      default:
+        logger.warn(
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: Unknown charging profile purpose: ${chargingProfile.chargingProfilePurpose}`
+        )
+        return false
+    }
+
+    logger.debug(
+      `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfilePurpose: Purpose validation passed for profile ${chargingProfile.id.toString()}`
+    )
+    return true
+  }
+
+  /**
+   * Validates an individual charging schedule within a charging profile
+   * @param chargingStation - The charging station instance
+   * @param schedule - The charging schedule to validate
+   * @param scheduleIndex - Index of the schedule in the profile's schedule array
+   * @param chargingProfile - The parent charging profile
+   * @param evseId - EVSE identifier
+   * @returns True if schedule is valid, false otherwise
+   */
+  private validateChargingSchedule (
+    chargingStation: ChargingStation,
+    schedule: OCPP20ChargingScheduleType,
+    scheduleIndex: number,
+    chargingProfile: OCPP20ChargingProfileType,
+    evseId: number
+  ): boolean {
+    logger.debug(
+      `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Validating schedule ${scheduleIndex.toString()} (ID: ${schedule.id.toString()}) in profile ${chargingProfile.id.toString()}`
+    )
+
+    // Validate schedule ID is positive
+    if (schedule.id <= 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Invalid schedule ID ${schedule.id.toString()}, must be positive`
+      )
+      return false
+    }
+
+    // Validate charging schedule periods array is not empty
+    if (schedule.chargingSchedulePeriod.length === 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Schedule must contain at least one charging schedule period`
+      )
+      return false
+    }
+
+    // Validate charging rate unit is valid (type system ensures it exists)
+    if (!Object.values(OCPP20ChargingRateUnitEnumType).includes(schedule.chargingRateUnit)) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Invalid charging rate unit: ${schedule.chargingRateUnit}`
+      )
+      return false
+    }
+
+    // Validate duration constraints
+    if (schedule.duration !== undefined && schedule.duration <= 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Schedule duration must be positive if specified`
+      )
+      return false
+    }
+
+    // Validate minimum charging rate if specified
+    if (schedule.minChargingRate !== undefined && schedule.minChargingRate < 0) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Minimum charging rate cannot be negative`
+      )
+      return false
+    }
+
+    // Validate start schedule time constraints
+    if (
+      schedule.startSchedule &&
+      chargingProfile.validFrom &&
+      schedule.startSchedule < chargingProfile.validFrom
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Schedule start time cannot be before profile validFrom`
+      )
+      return false
+    }
+
+    if (
+      schedule.startSchedule &&
+      chargingProfile.validTo &&
+      schedule.startSchedule >= chargingProfile.validTo
+    ) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Schedule start time must be before profile validTo`
+      )
+      return false
+    }
+
+    // Validate charging schedule periods
+    let previousStartPeriod = -1
+    for (const [periodIndex, period] of schedule.chargingSchedulePeriod.entries()) {
+      // Validate start period is non-negative and increasing
+      if (period.startPeriod < 0) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Period ${periodIndex.toString()} start time cannot be negative`
+        )
+        return false
+      }
+
+      if (period.startPeriod <= previousStartPeriod) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Period ${periodIndex.toString()} start time must be greater than previous period`
+        )
+        return false
+      }
+      previousStartPeriod = period.startPeriod
+
+      // Validate charging limit is positive
+      if (period.limit <= 0) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Period ${periodIndex.toString()} charging limit must be positive`
+        )
+        return false
+      }
+
+      // Validate minimum charging rate constraint
+      if (schedule.minChargingRate !== undefined && period.limit < schedule.minChargingRate) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Period ${periodIndex.toString()} limit cannot be below minimum charging rate`
+        )
+        return false
+      }
+
+      // Validate number of phases constraints
+      if (period.numberPhases !== undefined) {
+        if (period.numberPhases < 1 || period.numberPhases > 3) {
+          logger.warn(
+            `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Period ${periodIndex.toString()} number of phases must be 1-3`
+          )
+          return false
+        }
+
+        // If phaseToUse is specified, validate it's within the number of phases
+        if (
+          period.phaseToUse !== undefined &&
+          (period.phaseToUse < 1 || period.phaseToUse > period.numberPhases)
+        ) {
+          logger.warn(
+            `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Period ${periodIndex.toString()} phaseToUse must be between 1 and numberPhases`
+          )
+          return false
+        }
+      }
+    }
+
+    logger.debug(
+      `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Schedule ${scheduleIndex.toString()} validation passed`
+    )
     return true
   }
 
index 155b4c78512547d3ecba85846cabadeb7f9bbfaa..6201fdf1024ac392d73aedde30efea02d64d1083 100644 (file)
@@ -21,22 +21,72 @@ import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
 
 const moduleName = 'OCPP20RequestService'
 
+/**
+ * OCPP 2.0.1 Request Service
+ *
+ * Handles outgoing OCPP 2.0.1 requests from the charging station to the charging station management system (CSMS).
+ * This service is responsible for:
+ * - Building and validating request payloads according to OCPP 2.0.1 specification
+ * - Managing request-response cycles with enhanced error handling and status reporting
+ * - Ensuring message integrity through comprehensive JSON schema validation
+ * - Providing type-safe interfaces for all supported OCPP 2.0.1 commands
+ * - Supporting advanced OCPP 2.0.1 features like variables, components, and enhanced transaction management
+ *
+ * Key architectural improvements over OCPP 1.6:
+ * - Enhanced variable and component model support
+ * - Improved transaction lifecycle management with UUIDs
+ * - Advanced authorization capabilities with IdTokens
+ * - Comprehensive EVSE and connector state management
+ * - Extended security and certificate handling
+ * OCPPRequestService - Base class providing common OCPP functionality
+ */
 export class OCPP20RequestService extends OCPPRequestService {
   protected payloadValidatorFunctions: Map<OCPP20RequestCommand, ValidateFunction<JsonType>>
 
+  /**
+   * Constructs an OCPP 2.0.1 Request Service instance
+   *
+   * Initializes the service with OCPP 2.0.1-specific configurations including:
+   * - JSON schema validators for all supported OCPP 2.0.1 request commands
+   * - Enhanced payload validation with stricter type checking
+   * - Response service integration for comprehensive response handling
+   * - AJV validation setup optimized for OCPP 2.0.1's expanded message set
+   * - Support for advanced OCPP 2.0.1 features like variable management and enhanced security
+   * @param ocppResponseService - The response service instance for handling OCPP 2.0.1 responses
+   */
   public constructor (ocppResponseService: OCPPResponseService) {
-    // if (new.target.name === moduleName) {
-    //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
-    // }
     super(OCPPVersion.VERSION_201, ocppResponseService)
     this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
       OCPP20ServiceUtils.createRequestPayloadConfigs(),
-      OCPP20ServiceUtils.createRequestFactoryOptions(moduleName, 'constructor'),
+      OCPP20ServiceUtils.createRequestPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
+  /**
+   * Handles OCPP 2.0.1 request processing with enhanced validation and comprehensive error handling
+   *
+   * This method serves as the main entry point for all outgoing OCPP 2.0.1 requests to the CSMS.
+   * It performs advanced operations including:
+   * - Validates that the requested command is supported by the charging station configuration
+   * - Builds and validates request payloads according to strict OCPP 2.0.1 schemas
+   * - Handles OCPP 2.0.1-specific features like component/variable management and enhanced security
+   * - Sends requests with comprehensive error handling and detailed logging
+   * - Processes responses with full support for OCPP 2.0.1's enhanced status reporting
+   * - Manages advanced OCPP 2.0.1 concepts like EVSE management and transaction UUIDs
+   *
+   * The method ensures full compliance with OCPP 2.0.1 specification while providing
+   * enhanced type safety and detailed error reporting for debugging and monitoring.
+   * @template RequestType - The expected type of the request parameters
+   * @template ResponseType - The expected type of the response from the CSMS
+   * @param chargingStation - The charging station instance making the request
+   * @param commandName - The OCPP 2.0.1 command to execute (e.g., 'Authorize', 'TransactionEvent')
+   * @param commandParams - Optional parameters specific to the command being executed
+   * @param params - Optional request parameters for controlling request behavior
+   * @returns Promise resolving to the typed response from the CSMS
+   * @throws {OCPPError} When the command is not supported, validation fails, or CSMS returns an error
+   */
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
     chargingStation: ChargingStation,
@@ -47,7 +97,6 @@ export class OCPP20RequestService extends OCPPRequestService {
     logger.debug(
       `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Processing '${commandName}' request`
     )
-    // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
     if (OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
       try {
         logger.debug(
index b2bb8434ca850908078fba2bdfdec26242ec567b..6adcf378efe7c92bd402fb26ce177478f3fe1704 100644 (file)
@@ -77,9 +77,6 @@ export class OCPP20ResponseService extends OCPPResponseService {
   private readonly responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>
 
   public constructor () {
-    // if (new.target.name === moduleName) {
-    //   throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
-    // }
     super(OCPPVersion.VERSION_201)
     this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
       [
@@ -98,13 +95,13 @@ export class OCPP20ResponseService extends OCPPResponseService {
     ])
     this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
       OCPP20ServiceUtils.createResponsePayloadConfigs(),
-      OCPP20ServiceUtils.createResponseFactoryOptions(moduleName, 'constructor'),
+      OCPP20ServiceUtils.createResponsePayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.incomingRequestResponsePayloadValidateFunctions =
       OCPP20ServiceUtils.createPayloadValidatorMap(
         OCPP20ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
-        OCPP20ServiceUtils.createIncomingRequestResponseFactoryOptions(moduleName, 'constructor'),
+        OCPP20ServiceUtils.createIncomingRequestResponsePayloadOptions(moduleName, 'constructor'),
         this.ajvIncomingRequest
       )
     this.validatePayload = this.validatePayload.bind(this)
@@ -176,7 +173,7 @@ export class OCPP20ResponseService extends OCPPResponseService {
           payload,
           undefined,
           2
-        )} while the charging station is not registered on the central server`,
+        )} while the charging station is not registered on the CSMS`,
         commandName,
         payload
       )
@@ -220,7 +217,7 @@ export class OCPP20ResponseService extends OCPPResponseService {
       }
       const logMsg = `${chargingStation.logPrefix()} ${moduleName}.handleResponseBootNotification: Charging station in '${
         payload.status
-      }' state on the central server`
+      }' state on the CSMS`
       payload.status === RegistrationStatusEnumType.REJECTED
         ? logger.warn(logMsg)
         : logger.info(logMsg)
index 884983ec2e384964d9ef046b17099b3f3cc92c57..d2b8eb7becfd2788717ba11723c01eaae2db5d38 100644 (file)
@@ -73,23 +73,6 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
       methodName
     )
 
-  /**
-   * Factory options for OCPP 2.0 Incoming Request Response Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 incoming request response validators
-   */
-  public static createIncomingRequestResponseFactoryOptions = (
-    moduleName: string,
-    methodName: string
-  ) =>
-    OCPP20ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_201,
-      'assets/json-schemas/ocpp/2.0',
-      moduleName,
-      methodName
-    )
-
   /**
    * Configuration for OCPP 2.0 Incoming Request Response validators
    * @returns Array of validator configuration tuples
@@ -117,12 +100,15 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
   ]
 
   /**
-   * Factory options for OCPP 2.0 Request Service
+   * Factory options for OCPP 2.0 Incoming Request Response Service
    * @param moduleName - Name of the OCPP module
    * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 validators
+   * @returns Factory options object for OCPP 2.0 incoming request response validators
    */
-  public static createRequestFactoryOptions = (moduleName: string, methodName: string) =>
+  public static createIncomingRequestResponsePayloadOptions = (
+    moduleName: string,
+    methodName: string
+  ) =>
     OCPP20ServiceUtils.PayloadValidatorOptions(
       OCPPVersion.VERSION_201,
       'assets/json-schemas/ocpp/2.0',
@@ -157,12 +143,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
   ]
 
   /**
-   * Factory options for OCPP 2.0 Response Service
+   * Factory options for OCPP 2.0 Request Service
    * @param moduleName - Name of the OCPP module
    * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 response validators
+   * @returns Factory options object for OCPP 2.0 validators
    */
-  public static createResponseFactoryOptions = (moduleName: string, methodName: string) =>
+  public static createRequestPayloadOptions = (moduleName: string, methodName: string) =>
     OCPP20ServiceUtils.PayloadValidatorOptions(
       OCPPVersion.VERSION_201,
       'assets/json-schemas/ocpp/2.0',
@@ -196,6 +182,20 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     ],
   ]
 
+  /**
+   * Factory options for OCPP 2.0 Response Service
+   * @param moduleName - Name of the OCPP module
+   * @param methodName - Name of the method/command
+   * @returns Factory options object for OCPP 2.0 response validators
+   */
+  public static createResponsePayloadOptions = (moduleName: string, methodName: string) =>
+    OCPP20ServiceUtils.PayloadValidatorOptions(
+      OCPPVersion.VERSION_201,
+      'assets/json-schemas/ocpp/2.0',
+      moduleName,
+      methodName
+    )
+
   public static enforceMessageLimits<
     T extends { attributeType?: unknown; component: unknown; variable: unknown }
   >(
index c83377c346049253ec9457658aaf9bc4ce2164ce..8cc07e40bc63fb85c36053addd7085b4974a4e6e 100644 (file)
@@ -70,6 +70,8 @@ import { OCPP16Constants } from './1.6/OCPP16Constants.js'
 import { OCPP20Constants } from './2.0/OCPP20Constants.js'
 import { OCPPConstants } from './OCPPConstants.js'
 
+const moduleName = 'OCPPServiceUtils'
+
 type Ajv = _Ajv.default
 // eslint-disable-next-line @typescript-eslint/no-redeclare
 const Ajv = _Ajv.default
@@ -383,7 +385,7 @@ const validateSocMeasurandValue = (
     debug
   ) {
     logger.error(
-      `${chargingStation.logPrefix()} MeterValues measurand ${
+      `${chargingStation.logPrefix()} ${moduleName}.validateSocMeasurandValue: MeterValues measurand ${
         sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${socMinimumValue.toString()}/${sampledValue.value.toString()}/${socMaximumValue.toString()}`
@@ -605,7 +607,7 @@ const validateEnergyMeasurandValue = (
   if (energyValue > maxValue || energyValue < minValue || debug) {
     const connector = chargingStation.getConnectorStatus(connectorId)
     logger.error(
-      `${chargingStation.logPrefix()} MeterValues measurand ${
+      `${chargingStation.logPrefix()} ${moduleName}.validateEnergyMeasurandValue: MeterValues measurand ${
         sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${minValue.toString()}/${energyValue.toString()}/${maxValue.toString()}, duration: ${interval.toString()}ms`
@@ -826,7 +828,7 @@ const validatePowerMeasurandValue = (
     debug
   ) {
     logger.error(
-      `${chargingStation.logPrefix()} MeterValues measurand ${
+      `${chargingStation.logPrefix()} ${moduleName}.validatePowerMeasurandValue: MeterValues measurand ${
         sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumPower.toString()}/${sampledValue.value.toString()}/${connectorMaximumPower.toString()}`
@@ -849,7 +851,7 @@ const validateCurrentMeasurandValue = (
     debug
   ) {
     logger.error(
-      `${chargingStation.logPrefix()} MeterValues measurand ${
+      `${chargingStation.logPrefix()} ${moduleName}.validateCurrentMeasurandValue: MeterValues measurand ${
         sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       }: connector id ${connectorId.toString()}, transaction id ${connector?.transactionId?.toString()}, value: ${connectorMinimumAmperage.toString()}/${sampledValue.value.toString()}/${connectorMaximumAmperage.toString()}`
@@ -872,7 +874,7 @@ const validateCurrentMeasurandPhaseValue = (
     debug
   ) {
     logger.error(
-      `${chargingStation.logPrefix()} MeterValues measurand ${
+      `${chargingStation.logPrefix()} ${moduleName}.validateCurrentMeasurandPhaseValue: MeterValues measurand ${
         sampledValue.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
       }: phase ${
         // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -1592,12 +1594,12 @@ const getSampledValueTemplate = (
 }
 
 /**
- * Builds a sampled value object according to the specified OCPP version
- * @param ocppVersion The OCPP version to use for formatting the sampled value
- * @param sampledValueTemplate Template containing measurement configuration and metadata
- * @param value The measured numeric value to be included in the sampled value
- * @param context Optional context specifying when the measurement was taken (e.g., Sample.Periodic)
- * @param phase Optional phase information for multi-phase electrical measurements
+ * Builds a sampled value object according to the specified OCPP version.
+ * @param ocppVersion The OCPP version to use for formatting the sampled value
+ * @param sampledValueTemplate Template containing measurement configuration and metadata
+ * @param value The measured numeric value to be included in the sampled value
+ * @param context Optional context specifying when the measurement was taken (e.g., Sample.Periodic)
+ * @param phase Optional phase information for multi-phase electrical measurements
  * @returns A sampled value object formatted according to the specified OCPP version
  */
 function buildSampledValue (
@@ -1806,15 +1808,15 @@ export class OCPPServiceUtils {
   }
 
   /**
-   * Creates a Map of compiled OCPP payload validators from configurations
-   * Reduces code duplication across OCPP services
-   * @param configs Array of tuples containing command and validator configuration
-   * @param options Factory options including OCPP version, schema directory, etc.
-   * @param options.ocppVersion The OCPP version for schema validation
-   * @param options.schemaDir Directory path containing JSON schemas
-   * @param options.moduleName Name of the module for logging
-   * @param options.methodName Name of the method for logging
-   * @param ajvInstance Configured Ajv instance for validation
+   * Creates a Map of compiled OCPP payload validators from configurations.
+   * Reduces code duplication across OCPP services.
+   * @param configs Array of tuples containing command and validator configuration
+   * @param options Factory options including OCPP version, schema directory, etc.
+   * @param options.ocppVersion The OCPP version for schema validation
+   * @param options.schemaDir Directory path containing JSON schemas
+   * @param options.moduleName Name of the module for logging
+   * @param options.methodName Name of the method for logging
+   * @param ajvInstance Configured Ajv instance for validation
    * @returns Map of commands to their compiled validation functions
    */
   public static createPayloadValidatorMap<Command extends JsonType>(
@@ -1916,8 +1918,8 @@ export class OCPPServiceUtils {
   }
 
   /**
-   * Configuration for a single payload validator
-   * @param schemaPath Path to the JSON schema file
+   * Configuration for a single payload validator.
+   * @param schemaPath Path to the JSON schema file
    * @returns Configuration object for payload validator creation
    */
   public static readonly PayloadValidatorConfig = (schemaPath: string) =>
@@ -1926,11 +1928,11 @@ export class OCPPServiceUtils {
     }) as const
 
   /**
-   * Options for payload validator creation
-   * @param ocppVersion The OCPP version
-   * @param schemaDir Directory containing JSON schemas
-   * @param moduleName Name of the OCPP module
-   * @param methodName Name of the method/command
+   * Options for payload validator creation.
+   * @param ocppVersion The OCPP version
+   * @param schemaDir Directory containing JSON schemas
+   * @param moduleName Name of the OCPP module
+   * @param methodName Name of the method/command
    * @returns Options object for payload validator creation
    */
   public static readonly PayloadValidatorOptions = (
@@ -1947,12 +1949,12 @@ export class OCPPServiceUtils {
     }) as const
 
   /**
-   * Parses and loads a JSON schema file for OCPP payload validation
-   * Handles file reading, JSON parsing, and error recovery for schema validation
-   * @param relativePath Path to the schema file relative to the OCPP utils directory
-   * @param ocppVersion The OCPP version for error logging context
-   * @param moduleName Optional module name for error logging
-   * @param methodName Optional method name for error logging
+   * Parses and loads a JSON schema file for OCPP payload validation.
+   * Handles file reading, JSON parsing, and error recovery for schema validation.
+   * @param relativePath Path to the schema file relative to the OCPP utils directory
+   * @param ocppVersion The OCPP version for error logging context
+   * @param moduleName Optional module name for error logging
+   * @param methodName Optional method name for error logging
    * @returns Parsed JSON schema object or empty object if parsing fails
    */
   protected static parseJsonSchemaFile<T extends JsonType>(
index e95e1a864cdf48663663232409617277642c2fbe..17e34a759344013442622d1d99909edb775aa3f6 100644 (file)
@@ -124,7 +124,7 @@ await describe('E01 - Remote Start Transaction', async () => {
         mockChargingStation,
         invalidEvseRequest
       )
-    ).rejects.toThrow('EVSE 999 not found on charging station')
+    ).rejects.toThrow('EVSE 999 does not exist on charging station')
   })
 
   await it('Should reject RequestStartTransaction when connector is already occupied', async () => {