]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
fix(simulator): add connector Finishing state lifecycle simulation (#1227) (#1812)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 28 Apr 2026 12:06:34 +0000 (14:06 +0200)
committerGitHub <noreply@github.com>
Tue, 28 Apr 2026 12:06:34 +0000 (14:06 +0200)
* fix(simulator): add finishingStatusDelay tunable to station template

* fix(simulator): delay Available status after transaction end for OCPP 2.0.x

* fix(simulator): guard RemoteStartTransaction during Finishing state

* test(simulator): add finishing delay tests for OCPP 1.6 and 2.0.x

* docs(simulator): document finishingStatusDelay template tunable

* [autofix.ci] apply automated fixes

* refactor(simulator): audit corrections — rename, DRY, double-send, guard, safety

* [autofix.ci] apply automated fixes

* fix(simulator): fix JSDoc placement and document early return safety

* fix(simulator): address review comments — meter values, tests, docs

* fix(simulator): reset connector status before sending Available after delay

* fix(simulator): restore original operation ordering in delay=0 path

* fix(simulator): add debug log for RemoteStart rejection during Finishing state

* fix(simulator): harmonize log messages with existing codebase patterns

* refactor(simulator): extract DRY helpers and fix comment in StopTransaction handler

* fix(simulator): simplify Finishing guard to check status alone in RemoteStartTransaction

* fix(simulator): remove misleading log heuristic in OCPP 2.0 StartTransaction guard

* test(simulator): restructure postTransactionDelay tests per ModuleName-Feature naming convention

* fix(simulator): resolve Mock<Function> type mismatch in OCPP 2.0 postTransactionDelay test

* fix(simulator): prevent double power-divider decrement during shutdown with postTransactionDelay

* fix(simulator): prevent double-stop during shutdown using spec-compliant guards

* fix(simulator): restore resetConnectorStatus after sleep to prevent ATG race condition

* fix(simulator): reconcile stale connector state on boot when transactionId is missing

* [autofix.ci] apply automated fixes

* fix(simulator): also reconcile Finishing state connectors on boot for OCPP 1.6

* fix(simulator): delete transactionId before delay in OCPP 1.6 to prevent duplicate RemoteStop

* fix(simulator): use OCPP16ChargePointStatus in version-specific test code

* fix(simulator): harmonize log messages with existing codebase format

* fix(simulator): make OCPP 1.6 connector unlock unconditional per spec §4.10

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
13 files changed:
README.md
src/charging-station/Helpers.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPConnectorStatusOperations.ts
src/charging-station/ocpp/OCPPServiceOperations.ts
src/types/ChargingStationTemplate.ts
src/utils/Constants.ts
tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStartTransaction.test.ts
tests/charging-station/ocpp/1.6/OCPP16ResponseService-PostTransactionDelay.test.ts [new file with mode: 0644]
tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-PostTransactionDelay.test.ts [new file with mode: 0644]

index afc3c3722f436480b5a921d83c2190e36d2b1754..cef30888568b28269cfc6a0add0888749e61154c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -198,68 +198,69 @@ But the modifications to test have to be done to the files in the build target d
 
 **src/assets/station-templates/\<name\>.json**:
 
-| Key                                                  | Value(s)      | Default Value                                                                                                                      | Value type                                                                                                                                                                    | Description                                                                                                                                                                                                                                  |
-| ---------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| supervisionUrls                                      |               | []                                                                                                                                 | string \| string[]                                                                                                                                                            | string or strings array containing connection URIs to OCPP-J servers                                                                                                                                                                         |
-| supervisionUser                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication user to OCPP-J server                                                                                                                                                                                              |
-| supervisionPassword                                  |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication password to OCPP-J server                                                                                                                                                                                          |
-| supervisionUrlOcppConfiguration                      | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable supervision URL configuration via a vendor OCPP parameter key                                                                                                                                                                         |
-| supervisionUrlOcppKey                                |               | 'ConnectionUrl'                                                                                                                    | string                                                                                                                                                                        | the vendor string that will be used as a vendor OCPP parameter key to set the supervision URL                                                                                                                                                |
-| autoStart                                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable automatic start of added charging station from template                                                                                                                                                                               |
-| ocppVersion                                          | 1.6/2.0/2.0.1 | 1.6                                                                                                                                | string                                                                                                                                                                        | OCPP version                                                                                                                                                                                                                                 |
-| ocppProtocol                                         | json          | json                                                                                                                               | string                                                                                                                                                                        | OCPP protocol                                                                                                                                                                                                                                |
-| ocppStrictCompliance                                 | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable strict adherence to the OCPP version and protocol specifications with OCPP commands PDU validation against [OCA](https://www.openchargealliance.org/) JSON schemas                                                                    |
-| ocppPersistentConfiguration                          | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent OCPP parameters storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)                               |
-| stationInfoPersistentConfiguration                   | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent station information and specifications storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)        |
-| automaticTransactionGeneratorPersistentConfiguration | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent automatic transaction generator configuration storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations) |
-| wsOptions                                            |               | {}                                                                                                                                 | ClientOptions & ClientRequestArgs                                                                                                                                             | [ws](https://github.com/websockets/ws) and node.js [http](https://nodejs.org/api/http.html) clients options intersection                                                                                                                     |
-| idTagsFile                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | RFID tags list file relative to [src/assets](./src/assets) path                                                                                                                                                                              |
-| iccid                                                |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card ICCID                                                                                                                                                                                                                               |
-| imsi                                                 |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card IMSI                                                                                                                                                                                                                                |
-| baseName                                             |               | undefined                                                                                                                          | string                                                                                                                                                                        | base name to build charging stations id                                                                                                                                                                                                      |
-| nameSuffix                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | name suffix to build charging stations id                                                                                                                                                                                                    |
-| fixedName                                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | use the 'baseName' as the charging stations unique name                                                                                                                                                                                      |
-| chargePointModel                                     |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations model                                                                                                                                                                                                                      |
-| chargePointVendor                                    |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations vendor                                                                                                                                                                                                                     |
-| chargePointSerialNumberPrefix                        |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge point serial number prefix                                                                                                                                                                                                            |
-| chargeBoxSerialNumberPrefix                          |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge box serial number prefix (deprecated since OCPP 1.6)                                                                                                                                                                                  |
-| meterSerialNumberPrefix                              |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter serial number prefix                                                                                                                                                                                                                   |
-| meterType                                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter type                                                                                                                                                                                                                                   |
-| firmwareVersionPattern                               |               | Semantic versioning regular expression: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string | string                                                                                                                                                                        | charging stations firmware version pattern                                                                                                                                                                                                   |
-| firmwareVersion                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations firmware version                                                                                                                                                                                                           |
-| power                                                |               |                                                                                                                                    | float \| float[]                                                                                                                                                              | charging stations maximum power value(s)                                                                                                                                                                                                     |
-| powerSharedByConnectors                              | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | charging stations power shared by its connectors                                                                                                                                                                                             |
-| powerUnit                                            | W/kW          | W                                                                                                                                  | string                                                                                                                                                                        | charging stations power unit                                                                                                                                                                                                                 |
-| currentOutType                                       | AC/DC         | AC                                                                                                                                 | string                                                                                                                                                                        | charging stations current out type                                                                                                                                                                                                           |
-| voltageOut                                           |               | AC:230/DC:400                                                                                                                      | integer                                                                                                                                                                       | charging stations voltage out                                                                                                                                                                                                                |
-| numberOfPhases                                       | 0/1/3         | AC:3/DC:0                                                                                                                          | integer                                                                                                                                                                       | charging stations number of phase(s)                                                                                                                                                                                                         |
-| numberOfConnectors                                   |               |                                                                                                                                    | integer \| integer[]                                                                                                                                                          | charging stations number of connector(s)                                                                                                                                                                                                     |
-| useConnectorId0                                      | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | use connector id 0 definition from the charging station configuration template                                                                                                                                                               |
-| randomConnectors                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | randomize runtime connector id affectation from the connector id definition in charging station configuration template                                                                                                                       |
-| resetTime                                            |               | 60                                                                                                                                 | integer                                                                                                                                                                       | seconds to wait before the charging stations come back at reset                                                                                                                                                                              |
-| autoRegister                                         | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | set charging stations as registered at boot notification for testing purpose                                                                                                                                                                 |
-| autoReconnectMaxRetries                              |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | connection retries to the OCPP-J server                                                                                                                                                                                                      |
-| reconnectExponentialDelay                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | connection delay retry to the OCPP-J server                                                                                                                                                                                                  |
-| registrationMaxRetries                               |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | charging stations boot notification retries                                                                                                                                                                                                  |
-| amperageLimitationOcppKey                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations OCPP parameter key used to set the amperage limit, per phase for each connector on AC and global for DC                                                                                                                    |
-| amperageLimitationUnit                               | A/cA/dA/mA    | A                                                                                                                                  | string                                                                                                                                                                        | charging stations amperage limit unit                                                                                                                                                                                                        |
-| enableStatistics                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable charging stations statistics                                                                                                                                                                                                          |
-| remoteAuthorization                                  | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable RFID tags remote authorization                                                                                                                                                                                                        |
-| beginEndMeterValues                                  | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable Transaction.{Begin,End} MeterValues                                                                                                                                                                                                   |
-| outOfOrderEndMeterValues                             | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter)                                                                                                        |
-| meteringPerTransaction                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable metering history on a per transaction basis                                                                                                                                                                                           |
-| transactionDataMeterValues                           | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable transaction data MeterValues at stop transaction                                                                                                                                                                                      |
-| stopTransactionsOnStopped                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable stop transactions on charging station stop                                                                                                                                                                                            |
-| mainVoltageMeterValues                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | include charging stations main voltage MeterValues on three phased charging stations                                                                                                                                                         |
-| phaseLineToLineVoltageMeterValues                    | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | include charging stations line to line voltage MeterValues on three phased charging stations                                                                                                                                                 |
-| customValueLimitationMeterValues                     | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable limitation on custom fluctuated value in MeterValues                                                                                                                                                                                  |
-| firmwareUpgrade                                      |               | {<br />"versionUpgrade": {<br />"step": 1<br />},<br />"reset": true<br />}                                                        | {<br />versionUpgrade?: {<br />patternGroup?: number;<br />step?: number;<br />};<br />reset?: boolean;<br />failureStatus?: 'DownloadFailed' \| 'InstallationFailed';<br />} | Configuration section for simulating firmware upgrade support.                                                                                                                                                                               |
-| commandsSupport                                      |               | {<br />"incomingCommands": {},<br />"outgoingCommands": {}<br />}                                                                  | {<br /> incomingCommands: Record<IncomingRequestCommand, boolean>;<br />outgoingCommands?: Record<RequestCommand, boolean>;<br />}                                            | Configuration section for OCPP commands support. Empty section or subsections means all implemented OCPP commands are supported                                                                                                              |
-| messageTriggerSupport                                |               | {}                                                                                                                                 | Record<MessageTrigger, boolean>                                                                                                                                               | Configuration section for OCPP commands trigger support. Empty section means all implemented OCPP trigger commands are supported                                                                                                             |
-| Configuration                                        |               |                                                                                                                                    | ChargingStationOcppConfiguration                                                                                                                                              | charging stations OCPP parameters configuration section                                                                                                                                                                                      |
-| AutomaticTransactionGenerator                        |               |                                                                                                                                    | AutomaticTransactionGeneratorConfiguration                                                                                                                                    | charging stations ATG configuration section                                                                                                                                                                                                  |
-| Connectors                                           |               |                                                                                                                                    | Record<string, ConnectorStatus>                                                                                                                                               | charging stations connectors configuration section                                                                                                                                                                                           |
-| Evses                                                |               |                                                                                                                                    | Record<string, EvseTemplate>                                                                                                                                                  | charging stations EVSEs configuration section                                                                                                                                                                                                |
+| Key                                                  | Value(s)      | Default Value                                                                                                                      | Value type                                                                                                                                                                    | Description                                                                                                                                                                                                                                       |
+| ---------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| supervisionUrls                                      |               | []                                                                                                                                 | string \| string[]                                                                                                                                                            | string or strings array containing connection URIs to OCPP-J servers                                                                                                                                                                              |
+| supervisionUser                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication user to OCPP-J server                                                                                                                                                                                                   |
+| supervisionPassword                                  |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication password to OCPP-J server                                                                                                                                                                                               |
+| supervisionUrlOcppConfiguration                      | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable supervision URL configuration via a vendor OCPP parameter key                                                                                                                                                                              |
+| supervisionUrlOcppKey                                |               | 'ConnectionUrl'                                                                                                                    | string                                                                                                                                                                        | the vendor string that will be used as a vendor OCPP parameter key to set the supervision URL                                                                                                                                                     |
+| autoStart                                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable automatic start of added charging station from template                                                                                                                                                                                    |
+| ocppVersion                                          | 1.6/2.0/2.0.1 | 1.6                                                                                                                                | string                                                                                                                                                                        | OCPP version                                                                                                                                                                                                                                      |
+| ocppProtocol                                         | json          | json                                                                                                                               | string                                                                                                                                                                        | OCPP protocol                                                                                                                                                                                                                                     |
+| ocppStrictCompliance                                 | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable strict adherence to the OCPP version and protocol specifications with OCPP commands PDU validation against [OCA](https://www.openchargealliance.org/) JSON schemas                                                                         |
+| ocppPersistentConfiguration                          | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent OCPP parameters storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)                                    |
+| stationInfoPersistentConfiguration                   | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent station information and specifications storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)             |
+| automaticTransactionGeneratorPersistentConfiguration | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent automatic transaction generator configuration storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)      |
+| wsOptions                                            |               | {}                                                                                                                                 | ClientOptions & ClientRequestArgs                                                                                                                                             | [ws](https://github.com/websockets/ws) and node.js [http](https://nodejs.org/api/http.html) clients options intersection                                                                                                                          |
+| idTagsFile                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | RFID tags list file relative to [src/assets](./src/assets) path                                                                                                                                                                                   |
+| iccid                                                |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card ICCID                                                                                                                                                                                                                                    |
+| imsi                                                 |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card IMSI                                                                                                                                                                                                                                     |
+| baseName                                             |               | undefined                                                                                                                          | string                                                                                                                                                                        | base name to build charging stations id                                                                                                                                                                                                           |
+| nameSuffix                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | name suffix to build charging stations id                                                                                                                                                                                                         |
+| fixedName                                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | use the 'baseName' as the charging stations unique name                                                                                                                                                                                           |
+| chargePointModel                                     |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations model                                                                                                                                                                                                                           |
+| chargePointVendor                                    |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations vendor                                                                                                                                                                                                                          |
+| chargePointSerialNumberPrefix                        |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge point serial number prefix                                                                                                                                                                                                                 |
+| chargeBoxSerialNumberPrefix                          |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge box serial number prefix (deprecated since OCPP 1.6)                                                                                                                                                                                       |
+| meterSerialNumberPrefix                              |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter serial number prefix                                                                                                                                                                                                                        |
+| meterType                                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter type                                                                                                                                                                                                                                        |
+| firmwareVersionPattern                               |               | Semantic versioning regular expression: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string | string                                                                                                                                                                        | charging stations firmware version pattern                                                                                                                                                                                                        |
+| firmwareVersion                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations firmware version                                                                                                                                                                                                                |
+| power                                                |               |                                                                                                                                    | float \| float[]                                                                                                                                                              | charging stations maximum power value(s)                                                                                                                                                                                                          |
+| powerSharedByConnectors                              | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | charging stations power shared by its connectors                                                                                                                                                                                                  |
+| powerUnit                                            | W/kW          | W                                                                                                                                  | string                                                                                                                                                                        | charging stations power unit                                                                                                                                                                                                                      |
+| currentOutType                                       | AC/DC         | AC                                                                                                                                 | string                                                                                                                                                                        | charging stations current out type                                                                                                                                                                                                                |
+| voltageOut                                           |               | AC:230/DC:400                                                                                                                      | integer                                                                                                                                                                       | charging stations voltage out                                                                                                                                                                                                                     |
+| numberOfPhases                                       | 0/1/3         | AC:3/DC:0                                                                                                                          | integer                                                                                                                                                                       | charging stations number of phase(s)                                                                                                                                                                                                              |
+| numberOfConnectors                                   |               |                                                                                                                                    | integer \| integer[]                                                                                                                                                          | charging stations number of connector(s)                                                                                                                                                                                                          |
+| useConnectorId0                                      | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | use connector id 0 definition from the charging station configuration template                                                                                                                                                                    |
+| randomConnectors                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | randomize runtime connector id affectation from the connector id definition in charging station configuration template                                                                                                                            |
+| resetTime                                            |               | 60                                                                                                                                 | integer                                                                                                                                                                       | seconds to wait before the charging stations come back at reset                                                                                                                                                                                   |
+| autoRegister                                         | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | set charging stations as registered at boot notification for testing purpose                                                                                                                                                                      |
+| autoReconnectMaxRetries                              |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | connection retries to the OCPP-J server                                                                                                                                                                                                           |
+| reconnectExponentialDelay                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | connection delay retry to the OCPP-J server                                                                                                                                                                                                       |
+| registrationMaxRetries                               |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | charging stations boot notification retries                                                                                                                                                                                                       |
+| amperageLimitationOcppKey                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations OCPP parameter key used to set the amperage limit, per phase for each connector on AC and global for DC                                                                                                                         |
+| amperageLimitationUnit                               | A/cA/dA/mA    | A                                                                                                                                  | string                                                                                                                                                                        | charging stations amperage limit unit                                                                                                                                                                                                             |
+| enableStatistics                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable charging stations statistics                                                                                                                                                                                                               |
+| remoteAuthorization                                  | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable RFID tags remote authorization                                                                                                                                                                                                             |
+| beginEndMeterValues                                  | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable Transaction.{Begin,End} MeterValues                                                                                                                                                                                                        |
+| outOfOrderEndMeterValues                             | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter)                                                                                                             |
+| meteringPerTransaction                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable metering history on a per transaction basis                                                                                                                                                                                                |
+| transactionDataMeterValues                           | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable transaction data MeterValues at stop transaction                                                                                                                                                                                           |
+| stopTransactionsOnStopped                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable stop transactions on charging station stop                                                                                                                                                                                                 |
+| postTransactionDelay                                 | ≥ 0           | 0                                                                                                                                  | integer                                                                                                                                                                       | seconds to wait after transaction stop before transitioning connector to Available. Simulates cable-unplug delay. In OCPP 1.6 the connector stays in Finishing state; in OCPP 2.0.x it stays Occupied. 0 = immediate Available (default behavior) |
+| mainVoltageMeterValues                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | include charging stations main voltage MeterValues on three phased charging stations                                                                                                                                                              |
+| phaseLineToLineVoltageMeterValues                    | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | include charging stations line to line voltage MeterValues on three phased charging stations                                                                                                                                                      |
+| customValueLimitationMeterValues                     | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable limitation on custom fluctuated value in MeterValues                                                                                                                                                                                       |
+| firmwareUpgrade                                      |               | {<br />"versionUpgrade": {<br />"step": 1<br />},<br />"reset": true<br />}                                                        | {<br />versionUpgrade?: {<br />patternGroup?: number;<br />step?: number;<br />};<br />reset?: boolean;<br />failureStatus?: 'DownloadFailed' \| 'InstallationFailed';<br />} | Configuration section for simulating firmware upgrade support.                                                                                                                                                                                    |
+| commandsSupport                                      |               | {<br />"incomingCommands": {},<br />"outgoingCommands": {}<br />}                                                                  | {<br /> incomingCommands: Record<IncomingRequestCommand, boolean>;<br />outgoingCommands?: Record<RequestCommand, boolean>;<br />}                                            | Configuration section for OCPP commands support. Empty section or subsections means all implemented OCPP commands are supported                                                                                                                   |
+| messageTriggerSupport                                |               | {}                                                                                                                                 | Record<MessageTrigger, boolean>                                                                                                                                               | Configuration section for OCPP commands trigger support. Empty section means all implemented OCPP trigger commands are supported                                                                                                                  |
+| Configuration                                        |               |                                                                                                                                    | ChargingStationOcppConfiguration                                                                                                                                              | charging stations OCPP parameters configuration section                                                                                                                                                                                           |
+| AutomaticTransactionGenerator                        |               |                                                                                                                                    | AutomaticTransactionGeneratorConfiguration                                                                                                                                    | charging stations ATG configuration section                                                                                                                                                                                                       |
+| Connectors                                           |               |                                                                                                                                    | Record<string, ConnectorStatus>                                                                                                                                               | charging stations connectors configuration section                                                                                                                                                                                                |
+| Evses                                                |               |                                                                                                                                    | Record<string, EvseTemplate>                                                                                                                                                  | charging stations EVSEs configuration section                                                                                                                                                                                                     |
 
 #### Configuration section syntax example
 
index 568927da3310b5db0f9ed6fa40bc54c38c488cb6..c3c980266285e186130fe1c039b958528e8f32cb 100644 (file)
@@ -506,10 +506,20 @@ export const initializeConnectorsMapStatus = (
 ): void => {
   for (const [connectorId, connectorStatus] of connectors) {
     if (connectorId > 0 && connectorStatus.transactionStarted === true) {
-      logger.warn(
-        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-        `${logPrefix} ${moduleName}.initializeConnectorsMapStatus: Connector id ${connectorId.toString()} at initialization has a transaction started with id ${connectorStatus.transactionId?.toString()}`
-      )
+      if (
+        connectorStatus.transactionId == null ||
+        connectorStatus.status === ConnectorStatusEnum.Finishing
+      ) {
+        resetConnectorStatus(connectorStatus)
+        connectorStatus.locked = false
+        logger.warn(
+          `${logPrefix} ${moduleName}.initializeConnectorsMapStatus: Connector id ${connectorId.toString()} at initialization has stale transaction state, resetting`
+        )
+      } else {
+        logger.warn(
+          `${logPrefix} ${moduleName}.initializeConnectorsMapStatus: Connector id ${connectorId.toString()} at initialization has a transaction started with id ${connectorStatus.transactionId.toString()}`
+        )
+      }
     }
     if (connectorId === 0) {
       connectorStatus.availability = AvailabilityType.Operative
index 329fd3e1af8f48d8cd6e4404af670cb8971d3c11..090458b7b0a12516f37d1e74feb049bf75e11561 100644 (file)
@@ -1301,6 +1301,16 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         idTag
       )
     }
+    if (
+      chargingStation.getConnectorStatus(transactionConnectorId)?.status ===
+      OCPP16ChargePointStatus.Finishing
+    ) {
+      return this.notifyRemoteStartTransactionRejected(
+        chargingStation,
+        transactionConnectorId,
+        idTag
+      )
+    }
     if (
       !chargingStation.isChargingStationAvailable() ||
       !chargingStation.isConnectorAvailable(transactionConnectorId)
index 444c5124c6ed683026a5a9a9dac33edf5d1c0b81..a2a5d1a9173902f62a5d1730f456a980e6345453 100644 (file)
@@ -13,6 +13,7 @@ import {
 } from '../../../charging-station/index.js'
 import {
   ChargingStationEvents,
+  type ConnectorStatus,
   type JsonType,
   OCPP16AuthorizationStatus,
   type OCPP16AuthorizeRequest,
@@ -34,10 +35,11 @@ import {
   ReservationTerminationReason,
   type ResponseHandler,
 } from '../../../types/index.js'
-import { Constants, convertToInt, logger, truncateId } from '../../../utils/index.js'
+import { Constants, convertToInt, logger, sleep, truncateId } from '../../../utils/index.js'
 import {
   restoreConnectorStatus,
   sendAndSetConnectorStatus,
+  sendPostTransactionStatus,
 } from '../OCPPConnectorStatusOperations.js'
 import { OCPPResponseService } from '../OCPPResponseService.js'
 import { createPayloadValidatorMap, isRequestCommandSupported } from '../OCPPServiceUtils.js'
@@ -45,6 +47,32 @@ import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
 
 const moduleName = 'OCPP16ResponseService'
 
+const decrementPowerDivider = (chargingStation: ChargingStation): void => {
+  if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
+    if (chargingStation.powerDivider != null && chargingStation.powerDivider > 0) {
+      --chargingStation.powerDivider
+    } else {
+      logger.error(
+        `${chargingStation.logPrefix()} ${moduleName}.handleResponseStopTransaction: powerDivider is ${
+          chargingStation.powerDivider?.toString() ?? 'undefined'
+        }, cannot decrement`
+      )
+    }
+  }
+}
+
+const finalizeTransactionConnectorStatus = (
+  connectorStatus: ConnectorStatus | undefined,
+  requestPayload: OCPP16StopTransactionRequest
+): string | undefined => {
+  const transactionIdTag = requestPayload.idTag ?? connectorStatus?.transactionIdTag
+  resetConnectorStatus(connectorStatus)
+  if (connectorStatus != null) {
+    connectorStatus.locked = false
+  }
+  return transactionIdTag
+}
+
 /**
  * OCPP 1.6 Response Service - handles and processes all outgoing request responses
  * from the Charging Station (CP) to the Central System (CS) using OCPP 1.6 protocol.
@@ -507,41 +535,41 @@ export class OCPP16ResponseService extends OCPPResponseService {
         ],
         transactionId: requestPayload.transactionId,
       }))
-    if (
-      !chargingStation.isChargingStationAvailable() ||
-      !chargingStation.isConnectorAvailable(transactionConnectorId)
-    ) {
-      await sendAndSetConnectorStatus(chargingStation, {
-        connectorId: transactionConnectorId,
-        status: OCPP16ChargePointStatus.Unavailable,
-      } as OCPP16StatusNotificationRequest)
-    } else {
-      await sendAndSetConnectorStatus(chargingStation, {
-        connectorId: transactionConnectorId,
-        status: OCPP16ChargePointStatus.Available,
-      } as OCPP16StatusNotificationRequest)
-    }
-    if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
-      if (chargingStation.powerDivider != null && chargingStation.powerDivider > 0) {
-        --chargingStation.powerDivider
-      } else {
-        logger.error(
-          `${chargingStation.logPrefix()} ${moduleName}.handleResponseStopTransaction: powerDivider is ${
-            chargingStation.powerDivider?.toString() ?? 'undefined'
-          }, cannot decrement`
-        )
+    const postTransactionDelay = chargingStation.stationInfo?.postTransactionDelay ?? 0
+    let transactionIdTag: string | undefined
+    if (postTransactionDelay > 0) {
+      decrementPowerDivider(chargingStation)
+      // Send Finishing status if not already set (idempotency guard)
+      const transactionConnectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)
+      if (transactionConnectorStatus?.status !== OCPP16ChargePointStatus.Finishing) {
+        await sendAndSetConnectorStatus(chargingStation, {
+          connectorId: transactionConnectorId,
+          status: OCPP16ChargePointStatus.Finishing,
+        } as OCPP16StatusNotificationRequest)
       }
+      OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, transactionConnectorId)
+      if (transactionConnectorStatus != null) {
+        delete transactionConnectorStatus.transactionId
+      }
+      await sleep(secondsToMilliseconds(postTransactionDelay))
+      if (!chargingStation.started) {
+        return
+      }
+      transactionIdTag = finalizeTransactionConnectorStatus(
+        transactionConnectorStatus,
+        requestPayload
+      )
+      await sendPostTransactionStatus(chargingStation, transactionConnectorId)
+    } else {
+      await sendPostTransactionStatus(chargingStation, transactionConnectorId)
+      decrementPowerDivider(chargingStation)
+      const transactionConnectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)
+      transactionIdTag = finalizeTransactionConnectorStatus(
+        transactionConnectorStatus,
+        requestPayload
+      )
+      OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, transactionConnectorId)
     }
-    const transactionConnectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)
-    const transactionIdTag = requestPayload.idTag ?? transactionConnectorStatus?.transactionIdTag
-    resetConnectorStatus(transactionConnectorStatus)
-    if (
-      transactionConnectorStatus != null &&
-      (payload.idTagInfo == null || payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED)
-    ) {
-      transactionConnectorStatus.locked = false
-    }
-    OCPP16ServiceUtils.stopUpdatedMeterValues(chargingStation, transactionConnectorId)
     const logMsg = `${chargingStation.logPrefix()} ${moduleName}.handleResponseStopTransaction: Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${
       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       chargingStation.stationInfo?.chargingStationId
index c92b8460d6188b63347801d0c20c6821755ffa0b..b37ec067ec75f6b5c5eb8b17e6ea656916f3f6f8 100644 (file)
@@ -2508,7 +2508,8 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
 
     if (
       connectorStatus.transactionStarted === true ||
-      connectorStatus.transactionPending === true
+      connectorStatus.transactionPending === true ||
+      connectorStatus.locked === true
     ) {
       logger.warn(
         `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Connector ${connectorId.toString()} already has an active or pending transaction`
index 66360c3e1ccb1c602d00272fd293cf63accf4f98..a5f6a4c283c52ebe88e8292e291fc416edfb0e9d 100644 (file)
@@ -4,7 +4,7 @@ import { type ChargingStation, resetConnectorStatus } from '../../../charging-st
 import { OCPPError } from '../../../exception/index.js'
 import {
   type ConnectorStatus,
-  ConnectorStatusEnum,
+  type ConnectorStatusEnum,
   ErrorType,
   type MeterValue,
   OCPP20AuthorizationStatusEnumType,
@@ -49,6 +49,7 @@ import {
   generateUUID,
   isNotEmptyArray,
   logger,
+  sleep,
   validateIdentifierString,
 } from '../../../utils/index.js'
 import { buildConfigKey, getConfigurationKey } from '../../index.js'
@@ -57,7 +58,7 @@ import {
   mapOCPP20TokenType,
   OCPPAuthServiceFactory,
 } from '../auth/index.js'
-import { sendAndSetConnectorStatus } from '../OCPPConnectorStatusOperations.js'
+import { sendPostTransactionStatus } from '../OCPPConnectorStatusOperations.js'
 import {
   buildMeterValue,
   createPayloadConfigs,
@@ -199,17 +200,17 @@ export class OCPP20ServiceUtils {
       return
     }
     OCPP20ServiceUtils.stopUpdatedMeterValues(chargingStation, connectorId)
+    const postTransactionDelay = chargingStation.stationInfo?.postTransactionDelay ?? 0
+    if (postTransactionDelay > 0) {
+      delete connectorStatus.transactionId
+      await sleep(secondsToMilliseconds(postTransactionDelay))
+      if (!chargingStation.started) {
+        return
+      }
+    }
     resetConnectorStatus(connectorStatus)
     connectorStatus.locked = false
-    const targetStatus =
-      chargingStation.isChargingStationAvailable() &&
-      chargingStation.isConnectorAvailable(connectorId)
-        ? ConnectorStatusEnum.Available
-        : ConnectorStatusEnum.Unavailable
-    await sendAndSetConnectorStatus(chargingStation, {
-      connectorId,
-      connectorStatus: targetStatus,
-    } as unknown as OCPP20StatusNotificationRequest)
+    await sendPostTransactionStatus(chargingStation, connectorId)
   }
 
   /**
index d07e28099d0b5134fdb74f34de0166c3be6fee62..c221a63fa09ccaa989745111f87108b5f1b1e4f9 100644 (file)
@@ -48,6 +48,28 @@ export const sendAndSetConnectorStatus = async (
   })
 }
 
+/**
+ * Sends Available or Unavailable connector status after a transaction ends.
+ * Re-evaluates station and connector availability to determine the target status.
+ * @param chargingStation - Target charging station
+ * @param connectorId - Connector ID to transition
+ */
+export const sendPostTransactionStatus = async (
+  chargingStation: ChargingStation,
+  connectorId: number
+): Promise<void> => {
+  const status =
+    chargingStation.isChargingStationAvailable() &&
+    chargingStation.isConnectorAvailable(connectorId)
+      ? ConnectorStatusEnum.Available
+      : ConnectorStatusEnum.Unavailable
+  await sendAndSetConnectorStatus(chargingStation, {
+    connectorId,
+    connectorStatus: status,
+    status,
+  } as unknown as StatusNotificationRequest)
+}
+
 /**
  * Restores a connector status to Reserved or Available based on its current state.
  * @param chargingStation - Target charging station
index a2afb70daeff2e6f10f5f9ada222598f069b378f..44ffb05040e4c4fb0c32e071421966414fb86c22 100644 (file)
@@ -4,6 +4,7 @@ import { type ChargingStation } from '../../charging-station/index.js'
 import { OCPPError } from '../../exception/index.js'
 import {
   AuthorizationStatus,
+  ConnectorStatusEnum,
   ErrorType,
   OCPPVersion,
   type StartTransactionResult,
@@ -101,7 +102,10 @@ export const stopRunningTransactions = async (
   switch (chargingStation.stationInfo?.ocppVersion) {
     case OCPPVersion.VERSION_16: {
       for (const { connectorId, connectorStatus } of chargingStation.iterateConnectors(true)) {
-        if (connectorStatus.transactionStarted === true) {
+        if (
+          connectorStatus.transactionStarted === true &&
+          connectorStatus.status !== ConnectorStatusEnum.Finishing
+        ) {
           await OCPP16ServiceUtils.stopTransactionOnConnector(chargingStation, connectorId, reason)
         }
       }
index c17544f736c4b8e0d1d60254590e6bb1aee731a5..f88f24da36d288bd5f32fdf7e5dc03ad8d232e99 100644 (file)
@@ -94,6 +94,8 @@ export interface ChargingStationTemplate {
   /** @deprecated Replaced by ocppStrictCompliance. */
   payloadSchemaValidation?: boolean
   phaseLineToLineVoltageMeterValues?: boolean
+  /** Seconds to wait after transaction stop before transitioning connector to Available. Simulates cable-unplug delay. In OCPP 1.6 the connector stays in Finishing state; in OCPP 2.0.x it stays Occupied. 0 = immediate Available (default behavior). */
+  postTransactionDelay?: number
   power?: number | number[]
   powerSharedByConnectors?: boolean
   powerUnit?: PowerUnits
index 5953ec6311a8c358c60425694710195c8b11968b..837b74ebed4b7e17780b3b8d2773f044f328ec95 100644 (file)
@@ -80,6 +80,7 @@ export class Constants {
     ocppVersion: OCPPVersion.VERSION_16,
     outOfOrderEndMeterValues: false,
     phaseLineToLineVoltageMeterValues: false,
+    postTransactionDelay: 0,
     reconnectExponentialDelay: false,
     registrationMaxRetries: -1,
     remoteAuthorization: true,
index 2010ee0dcba4f9f6b185df0590ef958e4018bddc..664dd19dd4d4d4103cf8f539063b9b83bcdd0156 100644 (file)
@@ -15,6 +15,7 @@ import { OCPP16IncomingRequestService } from '../../../../src/charging-station/o
 import {
   AvailabilityType,
   GenericStatus,
+  OCPP16ChargePointStatus,
   OCPP16IncomingRequestCommand,
   OCPP16RequestCommand,
 } from '../../../../src/types/index.js'
@@ -226,6 +227,27 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     assert.strictEqual(response.status, GenericStatus.Rejected)
   })
 
+  // --- Finishing state guard ---
+
+  await it('should reject remote start transaction when connector is in Finishing state', async () => {
+    // Arrange
+    const { station, testableService } = testContext
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus != null) {
+      connectorStatus.status = OCPP16ChargePointStatus.Finishing
+    }
+    const request: RemoteStartTransactionRequest = {
+      connectorId: 1,
+      idTag: TEST_ID_TAG,
+    }
+
+    // Act
+    const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+    // Assert
+    assert.strictEqual(response.status, GenericStatus.Rejected)
+  })
+
   // --- Event listeners ---
 
   await describe('REMOTE_START_TRANSACTION event listener', async () => {
diff --git a/tests/charging-station/ocpp/1.6/OCPP16ResponseService-PostTransactionDelay.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-PostTransactionDelay.test.ts
new file mode 100644 (file)
index 0000000..d9e0019
--- /dev/null
@@ -0,0 +1,260 @@
+/**
+ * @file Tests for OCPP16ResponseService postTransactionDelay
+ * @description Verifies the postTransactionDelay feature in OCPP 1.6 StopTransaction response
+ * handling: Finishing→Available transitions, zero-delay immediate transitions,
+ * availability re-evaluation, and shutdown-during-delay safety.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type { OCPP16ResponseService } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import type {
+  OCPP16StopTransactionRequest,
+  OCPP16StopTransactionResponse,
+} from '../../../../src/types/index.js'
+
+import { OCPP16ServiceUtils } from '../../../../src/charging-station/ocpp/1.6/OCPP16ServiceUtils.js'
+import {
+  AvailabilityType,
+  OCPP16AuthorizationStatus,
+  OCPP16ChargePointStatus,
+  OCPP16RequestCommand,
+} from '../../../../src/types/index.js'
+import {
+  flushMicrotasks,
+  setupConnectorWithTransaction,
+  standardCleanup,
+  withMockTimers,
+} from '../../../helpers/TestLifecycleHelpers.js'
+import { createOCPP16ResponseTestContext, setMockRequestHandler } from './OCPP16TestUtils.js'
+
+await describe('OCPP16ResponseService — PostTransactionDelay', async () => {
+  let station: ChargingStation
+  let responseService: OCPP16ResponseService
+  let requestCalls: unknown[][]
+
+  beforeEach(() => {
+    const ctx = createOCPP16ResponseTestContext({
+      stationInfo: { postTransactionDelay: 5 },
+    })
+    station = ctx.station
+    responseService = ctx.responseService
+    station.started = true
+
+    requestCalls = []
+    setMockRequestHandler(station, (...args: unknown[]) => {
+      requestCalls.push(args)
+      return Promise.resolve({})
+    })
+
+    mock.method(OCPP16ServiceUtils, 'startUpdatedMeterValues', () => {
+      /* noop */
+    })
+    mock.method(OCPP16ServiceUtils, 'stopUpdatedMeterValues', () => {
+      /* noop */
+    })
+  })
+
+  afterEach(() => {
+    standardCleanup()
+  })
+
+  await it('should send Finishing then Available after configured delay', async t => {
+    // Arrange
+    setupConnectorWithTransaction(station, 1, { transactionId: 100 })
+    const requestPayload: OCPP16StopTransactionRequest = {
+      meterStop: 1000,
+      timestamp: new Date(),
+      transactionId: 100,
+    }
+    const responsePayload: OCPP16StopTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+    }
+
+    // Act
+    await withMockTimers(t, ['setTimeout'], async () => {
+      const promise = responseService.responseHandler(
+        station,
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        responsePayload,
+        requestPayload
+      )
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      t.mock.timers.tick(5000)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      await promise
+    })
+
+    // Assert
+    const statusCalls = requestCalls.filter(
+      call =>
+        call[1] === OCPP16RequestCommand.STATUS_NOTIFICATION &&
+        (call[2] as Record<string, unknown>).connectorId === 1
+    )
+    assert.ok(
+      statusCalls.length >= 2,
+      `Expected at least 2 status calls, got ${String(statusCalls.length)}`
+    )
+    assert.strictEqual(
+      (statusCalls[0][2] as Record<string, unknown>).status,
+      OCPP16ChargePointStatus.Finishing
+    )
+    assert.strictEqual(
+      (statusCalls[1][2] as Record<string, unknown>).status,
+      OCPP16ChargePointStatus.Available
+    )
+  })
+
+  await it('should send Available immediately when postTransactionDelay is 0', async () => {
+    // Arrange
+    assert.ok(station.stationInfo != null, 'stationInfo should be defined')
+    station.stationInfo.postTransactionDelay = 0
+    setupConnectorWithTransaction(station, 1, { transactionId: 200 })
+    const requestPayload: OCPP16StopTransactionRequest = {
+      meterStop: 2000,
+      timestamp: new Date(),
+      transactionId: 200,
+    }
+    const responsePayload: OCPP16StopTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+    }
+
+    // Act
+    await responseService.responseHandler(
+      station,
+      OCPP16RequestCommand.STOP_TRANSACTION,
+      responsePayload,
+      requestPayload
+    )
+
+    // Assert
+    const statusCalls = requestCalls.filter(
+      call =>
+        call[1] === OCPP16RequestCommand.STATUS_NOTIFICATION &&
+        (call[2] as Record<string, unknown>).connectorId === 1
+    )
+    const finishingCalls = statusCalls.filter(
+      call => (call[2] as Record<string, unknown>).status === OCPP16ChargePointStatus.Finishing
+    )
+    assert.strictEqual(
+      finishingCalls.length,
+      0,
+      'No Finishing status should be sent when delay is 0'
+    )
+    const availableCalls = statusCalls.filter(
+      call => (call[2] as Record<string, unknown>).status === OCPP16ChargePointStatus.Available
+    )
+    assert.ok(availableCalls.length >= 1, 'Should send Available status')
+  })
+
+  await it('should send Unavailable after delay when station becomes unavailable during finishing', async t => {
+    // Arrange
+    setupConnectorWithTransaction(station, 1, { transactionId: 300 })
+    const connector0 = station.getConnectorStatus(0)
+    if (connector0 != null) {
+      connector0.availability = AvailabilityType.Inoperative
+    }
+
+    const requestPayload: OCPP16StopTransactionRequest = {
+      meterStop: 3000,
+      timestamp: new Date(),
+      transactionId: 300,
+    }
+    const responsePayload: OCPP16StopTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+    }
+
+    // Act
+    await withMockTimers(t, ['setTimeout'], async () => {
+      const promise = responseService.responseHandler(
+        station,
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        responsePayload,
+        requestPayload
+      )
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      t.mock.timers.tick(5000)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      await promise
+    })
+
+    // Assert
+    const statusCalls = requestCalls.filter(
+      call =>
+        call[1] === OCPP16RequestCommand.STATUS_NOTIFICATION &&
+        (call[2] as Record<string, unknown>).connectorId === 1
+    )
+    assert.ok(
+      statusCalls.length >= 2,
+      `Expected at least 2 status calls, got ${String(statusCalls.length)}`
+    )
+    assert.strictEqual(
+      (statusCalls[0][2] as Record<string, unknown>).status,
+      OCPP16ChargePointStatus.Finishing
+    )
+    assert.strictEqual(
+      (statusCalls[1][2] as Record<string, unknown>).status,
+      OCPP16ChargePointStatus.Unavailable
+    )
+  })
+
+  await it('should skip cleanup when station stops during delay', async t => {
+    // Arrange
+    setupConnectorWithTransaction(station, 1, { transactionId: 400 })
+    const requestPayload: OCPP16StopTransactionRequest = {
+      meterStop: 4000,
+      timestamp: new Date(),
+      transactionId: 400,
+    }
+    const responsePayload: OCPP16StopTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+    }
+
+    // Act
+    await withMockTimers(t, ['setTimeout'], async () => {
+      const promise = responseService.responseHandler(
+        station,
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        responsePayload,
+        requestPayload
+      )
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      station.started = false
+      t.mock.timers.tick(5000)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      await promise
+    })
+
+    // Assert
+    const statusCalls = requestCalls.filter(
+      call =>
+        call[1] === OCPP16RequestCommand.STATUS_NOTIFICATION &&
+        (call[2] as Record<string, unknown>).connectorId === 1
+    )
+    assert.strictEqual(statusCalls.length, 1, 'Only Finishing status should be sent')
+    assert.strictEqual(
+      (statusCalls[0][2] as Record<string, unknown>).status,
+      OCPP16ChargePointStatus.Finishing
+    )
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus == null) {
+      assert.fail('Expected connector 1 to exist')
+    }
+    assert.strictEqual(connectorStatus.transactionStarted, true)
+    assert.strictEqual(connectorStatus.transactionId, undefined)
+  })
+})
diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-PostTransactionDelay.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-PostTransactionDelay.test.ts
new file mode 100644 (file)
index 0000000..81d226c
--- /dev/null
@@ -0,0 +1,119 @@
+/**
+ * @file Tests for OCPP20ServiceUtils postTransactionDelay
+ * @description Verifies the postTransactionDelay feature in OCPP 2.0.x cleanupEndedTransaction:
+ * delayed Available transitions, zero-delay immediate transitions,
+ * and shutdown-during-delay safety.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type { ConnectorStatus } from '../../../../src/types/index.js'
+
+import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
+import { OCPPVersion } from '../../../../src/types/index.js'
+import {
+  flushMicrotasks,
+  standardCleanup,
+  withMockTimers,
+} from '../../../helpers/TestLifecycleHelpers.js'
+import { createMockChargingStation } from '../../helpers/StationHelpers.js'
+
+await describe('OCPP20ServiceUtils — PostTransactionDelay', async () => {
+  let station: ChargingStation
+  let connectorStatus: ConnectorStatus
+  let requestHandlerMock: ReturnType<typeof mock.fn>
+
+  beforeEach(() => {
+    const requestHandler = mock.fn(async () => Promise.resolve({}))
+    requestHandlerMock = requestHandler
+    const result = createMockChargingStation({
+      connectorsCount: 1,
+      ocppRequestService: {
+        requestHandler: requestHandler as (...args: unknown[]) => Promise<unknown>,
+      },
+      ocppVersion: OCPPVersion.VERSION_20,
+      started: true,
+      stationInfo: {
+        ocppVersion: OCPPVersion.VERSION_20,
+        postTransactionDelay: 3,
+      },
+    })
+    station = result.station
+    const cs = station.getConnectorStatus(1)
+    if (cs == null) {
+      throw new Error('Expected connector 1 to exist')
+    }
+    connectorStatus = cs
+    connectorStatus.transactionStarted = true
+    connectorStatus.transactionId = 'tx-1'
+    connectorStatus.locked = true
+  })
+
+  afterEach(() => {
+    standardCleanup()
+  })
+
+  await it('should delay Available transition after transaction end', async t => {
+    // Act
+    await withMockTimers(t, ['setTimeout'], async () => {
+      const promise = OCPP20ServiceUtils.cleanupEndedTransaction(station, 1, connectorStatus)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      t.mock.timers.tick(3000)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      await promise
+    })
+
+    // Assert
+    assert.strictEqual(connectorStatus.transactionStarted, false)
+    assert.strictEqual(connectorStatus.transactionId, undefined)
+    assert.strictEqual(connectorStatus.locked, false)
+    assert.ok(requestHandlerMock.mock.calls.length >= 1, 'Should send StatusNotification')
+  })
+
+  await it('should send Available immediately when postTransactionDelay is 0', async () => {
+    // Arrange
+    assert.ok(station.stationInfo != null, 'stationInfo should be defined')
+    station.stationInfo.postTransactionDelay = 0
+
+    // Act
+    await OCPP20ServiceUtils.cleanupEndedTransaction(station, 1, connectorStatus)
+
+    // Assert
+    assert.strictEqual(connectorStatus.transactionStarted, false)
+    assert.strictEqual(connectorStatus.transactionId, undefined)
+    assert.strictEqual(connectorStatus.locked, false)
+    assert.ok(requestHandlerMock.mock.calls.length >= 1, 'Should send StatusNotification')
+  })
+
+  await it('should skip cleanup when station stops during delay', async t => {
+    // Act
+    await withMockTimers(t, ['setTimeout'], async () => {
+      const promise = OCPP20ServiceUtils.cleanupEndedTransaction(station, 1, connectorStatus)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      station.started = false
+      t.mock.timers.tick(3000)
+      for (let i = 0; i < 10; i++) {
+        await flushMicrotasks()
+      }
+      await promise
+    })
+
+    // Assert — transactionStarted stays true (blocks ATG), transactionId cleared (blocks stopAll)
+    assert.strictEqual(connectorStatus.transactionStarted, true)
+    assert.strictEqual(connectorStatus.transactionId, undefined)
+    assert.strictEqual(connectorStatus.locked, true)
+    assert.strictEqual(
+      requestHandlerMock.mock.calls.length,
+      0,
+      'No StatusNotification should be sent'
+    )
+  })
+})