OCPP20AuthAdapter.authorizeRemote() was incorrectly sending a
TransactionEvent(Started) as a proxy for authorization, causing a
duplicate Started event when ATG subsequently called
startTransactionOnConnector(). Implement the proper OCPP 2.0.1
Authorize request/response flow, harmonizing the auth+start sequence
with OCPP 1.6: Authorize first, then TransactionEvent(Started).
Jérôme Benoit [Fri, 20 Mar 2026 19:10:38 +0000 (20:10 +0100)]
fix(ocpp2.0): guard BroadcastChannel MeterValues for both VERSION_20 and VERSION_201
The early return in handleMeterValues only checked VERSION_201, missing
VERSION_20. A station configured with ocppVersion '2.0' would fall
through to the OCPP 1.6 path and call convertToInt on a UUID string.
Jérôme Benoit [Fri, 20 Mar 2026 19:05:22 +0000 (20:05 +0100)]
fix(ocpp2.0): pass real transactionId to buildMeterValue for energy accumulation
buildMeterValue was called with transactionId=0 for OCPP 2.0 stations,
but transaction IDs are UUID strings in 2.0.1. The energy lookup
compared 'uuid-string' === 0, always returning 0 Wh. Pass the actual
connectorStatus.transactionId and widen the parameter type to
number | string | undefined.
Jérôme Benoit [Fri, 20 Mar 2026 19:00:23 +0000 (20:00 +0100)]
refactor(webui): restore native table layout to fix column overlap on action panel open
Replace display:flex overrides on <table>/<tr>/<td> with native HTML table
layout. Add overflow-x:auto wrapper with min-width floor so columns scroll
horizontally instead of compressing and overlapping when the action panel
reduces available width.
Jérôme Benoit [Fri, 20 Mar 2026 18:49:23 +0000 (19:49 +0100)]
fix(auth): log actual station OCPP version in RemoteAuthStrategy
Deduplicate adapter validation in initialize() to avoid logging the
same adapter twice for VERSION_20/VERSION_201 aliases. Add ocppVersion
to AuthConfiguration so strategies log the station's configured version
instead of internal map keys.
Jérôme Benoit [Fri, 20 Mar 2026 18:23:38 +0000 (19:23 +0100)]
fix(ocpp2.0): align VARIABLE_REGISTRY spec conformance and log severity
Distinguish required vs optional variables per OCPP 2.0.1 spec
(dm_components_vars.csv). Missing required variables log as error,
missing optional ones as warning. Tag 58 spec-required entries with
required: true and 6 non-spec entries with vendorSpecific: true.
Jérôme Benoit [Fri, 20 Mar 2026 17:07:17 +0000 (18:07 +0100)]
refactor: build version-agnostic OCPP transaction primitives in service layer (#1741)
* refactor: add StopTransactionResult type and OCPP 2.0 reason mapping
* refactor: extract OCPP 1.6 stopTransactionOnConnector to OCPP16ServiceUtils
* refactor: add unified stopTransactionOnConnector and stopRunningTransactions to OCPPServiceUtils
Add two version-dispatching functions to OCPPServiceUtils:
- stopTransactionOnConnector: dispatches to OCPP16ServiceUtils or
OCPP20ServiceUtils via dynamic import, returns StopTransactionResult
- stopRunningTransactions: sequential for OCPP 1.6, parallel (Promise.all)
for OCPP 2.0, includes transactionPending check for OCPP 2.0
Both use dynamic imports to avoid circular dependencies. stopRunningTransactions
is exposed as OCPPServiceUtils.stopRunningTransactions class member.
stopTransactionOnConnector is exported as standalone function only (class member
omitted due to OCPP16ServiceUtils override type conflict).
Includes 6 tests covering both OCPP versions and error cases.
* refactor: simplify ChargingStation and ATG to use OCPPServiceUtils unified methods
- ChargingStation.stopRunningTransactions: delegate to standalone function from OCPPServiceUtils
- Remove ChargingStation.stopRunningTransactionsOCPP20 private method (logic now in standalone function)
- Remove OCPP20ReasonEnumType import from ChargingStation (no longer needed)
- AutomaticTransactionGenerator.stopTransaction: call stopTransactionOnConnector standalone function
- Update ATG return type to Promise<StopTransactionResult | undefined>
- Use result.accepted instead of stopResponse.idTagInfo?.status === AuthorizationStatus.ACCEPTED
- Remove TODO comment from ATG.stopTransaction
* refactor: remove stopTransactionOnConnector from ChargingStation public API
* refactor: remove stopTransactionOnConnector from ChargingStation public API
- Remove ChargingStation.stopTransactionOnConnector public method
- Remove unused StopTransactionRequest, StopTransactionResponse, buildTransactionEndMeterValue, OCPP16ServiceUtils imports from ChargingStation
- Update OCPP16IncomingRequestService.handleRequestUnlockConnector to call OCPP16ServiceUtils.stopTransactionOnConnector directly
- Update test mocks in RemoteStopUnlock tests to target OCPP16ServiceUtils
- Fix OCPPServiceUtils-StopTransaction test: use args-based pattern for command detection, add proper JSDoc, fix type issues
* refactor(ocpp2.0): deduplicate stop pattern via stopAllTransactions
- Add OCPP20ServiceUtils.stopAllTransactions() — single factored function for parallel
EVSE iteration + requestStopTransaction, supports optional evseId for single-EVSE scope
- terminateAllTransactions delegates to stopAllTransactions(station, ResetCommand, reason)
- terminateEvseTransactions delegates to stopAllTransactions(station, ResetCommand, reason, evseId)
- stopRunningTransactions OCPP 2.0 path delegates to stopAllTransactions(station, trigger, stopped)
- Eliminates 3x duplicated iteration+parallel-stop pattern
* refactor: add startTransactionOnConnector and flushQueuedTransactionMessages abstractions
- Add startTransactionOnConnector to OCPPServiceUtils (version dispatch via dynamic imports)
OCPP 1.6: sends START_TRANSACTION via new OCPP16ServiceUtils.startTransactionOnConnector
OCPP 2.0: sends TransactionEvent(Started) via OCPP20ServiceUtils.sendTransactionEvent
Returns StartTransactionResult { accepted: boolean }
- Add flushQueuedTransactionMessages to OCPPServiceUtils (OCPP 1.6: no-op, OCPP 2.0: flushes queue)
- Migrate ATG startTransaction to use startTransactionOnConnector (fixes OCPP 2.0 ATG start)
- Migrate ATG handleStartTransactionResponse to handleStartTransactionResult (uses unified type)
- Remove ATG dependency on AuthorizationStatus, RequestCommand, StartTransactionRequest/Response
- Remove ChargingStation.flushQueuedTransactionEvents private method
- Remove version check in ChargingStation boot handler (line 2304)
- Add StartTransactionResult type to types barrel
* refactor: extract periodic meter values to OCPP service layer
- Add OCPP16ServiceUtils.startPeriodicMeterValues/stopPeriodicMeterValues
- Add OCPP20ServiceUtils.startPeriodicMeterValues/stopPeriodicMeterValues
- Add OCPPServiceUtils.startPeriodicMeterValues/stopPeriodicMeterValues (version dispatch)
- Remove startMeterValues, stopMeterValues, startTxUpdatedInterval, stopTxUpdatedInterval,
restartMeterValues from ChargingStation.ts
- Migrate all callers to use versioned ServiceUtils methods
- Fix test referencing removed startTxUpdatedInterval method
- ChargingStation.ts: -130 lines of version-specific meter values logic removed
* fix: update tests for renamed APIs and removed ChargingStation methods
- AutomaticTransactionGenerator.test.ts: handleStartTransactionResponse -> handleStartTransactionResult, use StartTransactionResult { accepted } instead of StartTransactionResponse
- ChargingStation-Transactions.test.ts: test already uses OCPP16ServiceUtils.stopPeriodicMeterValues
- OCPP20ServiceUtils-TransactionEvent.test.ts: startTxUpdatedInterval -> startPeriodicMeterValues
- StationHelpers.ts: remove stopMeterValues, startMeterValues, startTxUpdatedInterval, stopTxUpdatedInterval, restartMeterValues from mock (no longer on ChargingStation)
- OCPP16ServiceUtils.stopPeriodicMeterValues: add missing delete after clearInterval
* fix: correct startPeriodicMeterValues test to verify no-transaction guard instead of version guard
* fix: guard undefined evseId and avoid throw in shutdown paths
- stopTransactionOnConnector: guard getEvseIdByConnectorId returning undefined, return { accepted: false } with warn log
- stopRunningTransactions: replace throw with warn log in default branch (shutdown path must not crash)
* fix: deduplicate test coverage and address review findings
- Remove ChargingStation-StopRunningTransactions.test.ts (duplicated coverage with OCPPServiceUtils-StopTransaction.test.ts)
- Move error handling test to OCPPServiceUtils-StopTransaction.test.ts
- Guard undefined evseId in stopTransactionOnConnector (review finding)
- Replace throw with warn in stopRunningTransactions default branch (review finding)
* fix: generate transactionId for OCPP 2.0 start and align offline stop acceptance
- startTransactionOnConnector OCPP 2.0: generate UUID transactionId and reset seqNo when
connector has no transactionId (ATG path)
- stopTransactionOnConnector OCPP 2.0: treat missing idTokenInfo as accepted (offline queued
events return undefined idTokenInfo, consistent with startTransactionOnConnector)
* fix: map Other reason correctly and use static generateUUID import
- mapStopReasonToOCPP20: add explicit Other -> Other/AbnormalCondition mapping
instead of falling through to Local/StopAuthorized
- startTransactionOnConnector: use statically imported generateUUID instead of
unnecessary dynamic import (utils/index.js already imported at top of file)
* test: add coverage for startTransactionOnConnector, flushQueuedTransactionMessages, and mapStopReasonToOCPP20
Jérôme Benoit [Thu, 19 Mar 2026 15:27:49 +0000 (16:27 +0100)]
refactor(webui): harmonize barrel usage in types and composables
Add OCPP16AvailabilityType, OCPP16ChargePointStatus, and
OCPP16RegistrationStatus to the types barrel. Redirect all direct
module imports in tests to use barrels (types/, composables/).
Jérôme Benoit [Thu, 19 Mar 2026 15:18:42 +0000 (16:18 +0100)]
fix: merge duplicate type imports from same barrel module
Consolidate split import type statements from types/index.js into
single imports in OCPP16TestUtils.ts and OCPP20IncomingRequestService-
MasterPass.test.ts.
Jérôme Benoit [Thu, 19 Mar 2026 15:11:03 +0000 (16:11 +0100)]
refactor: harmonize barrel usage across all test files
Redirect all direct module imports (utils/Logger, utils/Constants,
worker/WorkerTypes, types/*, charging-station/ChargingStation) to
their respective barrel index.js in test files. Unit tests of the
module itself retain direct imports per convention.
Jérôme Benoit [Thu, 19 Mar 2026 14:53:24 +0000 (15:53 +0100)]
fix(ocpp): keep StopTransactionRequest as 1.6 wire type, narrow reason via indexed access
Revert the Omit-based widening of StopTransactionRequest — it's a 1.6
wire protocol message and should not accept OCPP 2.0 reason values.
Use StopTransactionRequest['reason'] indexed access type for narrowing
in ChargingStation, avoiding any version-specific OCPP16* import.
Jérôme Benoit [Thu, 19 Mar 2026 14:42:59 +0000 (15:42 +0100)]
fix(ocpp): remove type re-export from services barrel
Move OCPP20TransactionEventEnumType and OCPP20TriggerReasonEnumType
imports in ChargingStation.ts from the services barrel (ocpp/index.ts)
to the types barrel (types/index.ts) where they belong.
Jérôme Benoit [Thu, 19 Mar 2026 14:24:42 +0000 (15:24 +0100)]
refactor(ocpp): consolidate cross-stack types and harmonize barrel imports
Consolidate OCPP types with common semantics across 1.6 and 2.0 stacks
(AuthorizationStatus, StopTransactionReason, MessageTrigger, DataTransfer,
TriggerMessageStatus, UnlockStatus, AvailabilityStatus, HeartbeatRequest/
Response, StatusNotificationResponse, FirmwareStatusNotificationResponse).
Harmonize all imports across src/ and tests/ to use the barrel
(types/index.ts) instead of direct version-specific type paths, matching
the pattern already established by the OCPP 1.6 handlers.
Jérôme Benoit [Thu, 19 Mar 2026 11:17:44 +0000 (12:17 +0100)]
fix(ocpp2): audit TransactionEvent — state ownership, deauthorization, meter values
- Fix periodic TransactionEvent(Updated) to include meter values via buildMeterValue
- Replace non-UUID temp transactionId with generateUUID in OCPP20AuthAdapter
- Refactor state ownership: response handler is sole authority for transactionStarted,
StatusNotification(Occupied), and TxUpdatedInterval start
- Add transactionPending flag to prevent duplicate RequestStartTransaction race conditions
- Add requestDeauthorizeTransaction per E05.FR.09/FR.10/E06.FR.04: sends
Updated(Deauthorized, SuspendedEVSE) then Ended(Deauthorized, DeAuthorized)
- Fix rejection check to cover all non-Accepted idTokenInfo statuses
- Extract buildFinalMeterValues helper to eliminate DRY violation
- Skip Occupied/TxUpdatedInterval setup when idToken is rejected in same response
- Remove cleanup from Ended response handler (owned by caller)
- Add tests for getTxUpdatedInterval, requestDeauthorizeTransaction, Updated-failure path
Jérôme Benoit [Thu, 19 Mar 2026 00:08:33 +0000 (01:08 +0100)]
fix(ocpp2): handle transaction lifecycle in TransactionEvent response
Adapt the OCPP 1.6 transaction lifecycle pattern to OCPP 2.0: manage
connector state (transactionStarted, transactionId, StatusNotification,
meter intervals) in handleResponseTransactionEvent for both Started
and Ended events, matching handleResponseStartTransaction and
handleResponseStopTransaction in OCPP 1.6.
Jérôme Benoit [Wed, 18 Mar 2026 23:32:29 +0000 (00:32 +0100)]
feat(ui): add Catppuccin Latte light theme
Map Catppuccin Latte palette to semantic tokens following the official
style guide: Base for background, Mantle/Crust for secondary panes,
Surface 0/1 for hover/active, Blue for buttons/links, Lavender for
accent, Base for text-on-button (On Accent per guide).
Jérôme Benoit [Wed, 18 Mar 2026 23:23:29 +0000 (00:23 +0100)]
feat(ui): add configurable theme support
Add theme field to ConfigurationData. Theme CSS files are loaded
dynamically from assets/themes/ at startup. Falls back to
tokyo-night-storm when not configured or theme not found.
Move theme.css to assets/themes/tokyo-night-storm.css.
Jérôme Benoit [Wed, 18 Mar 2026 23:14:13 +0000 (00:14 +0100)]
feat(ui): apply Tokyo Night Storm theme with semantic CSS tokens
Add theme.css with two-layer token system: primitive tokens from
the official Tokyo Night Storm palette, semantic tokens mapping UI
roles to primitives. All components use semantic tokens exclusively.
Replace all hardcoded colors across 13 Vue components. Theme native
HTML elements (button, input, select, a, headings) globally. Toggle
button pressed state uses palette-semantic active bg + accent border
+ inset shadow for clear visual distinction.
Remove dead code: simulatorButtonClass computed and associated CSS
classes that duplicated global button styles.
Jérôme Benoit [Wed, 18 Mar 2026 22:21:10 +0000 (23:21 +0100)]
fix: use truncateId consistently for all user identifiers in logs
Replace 16 bare idTag/identifier.value references and 4 manual
substring(0,8) truncations with truncateId() across OCPP 1.6
ResponseService, IncomingRequestService, auth adapters, strategies,
and helpers.
User identifiers (RFID tags, auth tokens) are now consistently
truncated in all log output to prevent sensitive data exposure.
Jérôme Benoit [Wed, 18 Mar 2026 20:33:51 +0000 (21:33 +0100)]
fix: prevent tests from polluting production log files
Set NODE_ENV=test via cross-env in all three test scripts (test,
test:debug, test:coverage). Logger checks NODE_ENV and sets silent
mode, preventing file writes to logs/combined.log and logs/error.log
during test runs.
Jérôme Benoit [Wed, 18 Mar 2026 19:53:19 +0000 (20:53 +0100)]
fix(ocpp): replace this.constructor.name with moduleName in base class logs
Minification mangles class names, causing logs like 'ln.responseHandler'
instead of 'OCPP20ResponseService.responseHandler' in production builds.
Add abstract moduleName property to OCPPResponseService and
OCPPIncomingRequestService base classes, implemented by all four
subclasses using their file-level moduleName constant.
Jérôme Benoit [Wed, 18 Mar 2026 19:04:12 +0000 (20:04 +0100)]
refactor(test): remove duplicate certificate passthrough tests from broadcast channel
These 3 tests duplicated coverage already provided by
OCPP20RequestService-ISO15118.test.ts which tests the same code path
through the full requestHandler pipeline.
Add JSDoc descriptions to buildTransactionEvent implementation to
satisfy jsdoc/require-param-description lint rule.
Jérôme Benoit [Wed, 18 Mar 2026 18:32:25 +0000 (19:32 +0100)]
refactor(ocpp2): extract buildTransactionEvent as standalone function, remove dead context code
Extract buildTransactionEvent from OCPP20ServiceUtils static method to
exported standalone function, consistent with buildStatusNotificationRequest
and buildMeterValue.
Remove unused context overload from buildTransactionEvent and
sendTransactionEvent — all production callers pass triggerReason directly.
Remove dead code: OCPP20TransactionContext interface (not in OCPP 2.0.1
specs), selectTriggerReason method, TransactionContextFixtures, and
associated tests.
Jérôme Benoit [Wed, 18 Mar 2026 16:59:56 +0000 (17:59 +0100)]
refactor(ocpp): add rawPayload bypass, resolve minimal params in buildRequestPayload, fix test types
Add rawPayload option to RequestParams for offline queue replay to
explicitly bypass buildRequestPayload instead of heuristic detection.
Make buildRequestPayload resolve missing TransactionEvent fields from
station context (connectorId from evse, transactionId generation,
triggerReason defaults) so the broadcast channel passthrough works.
Remove pre-built payload guard from OCPP 1.6 STATUS_NOTIFICATION.
Replace Record<string, unknown> downgrades in tests with proper OCPP
types (Partial<OCPP20TransactionEventRequest>, RequestParams).
Jérôme Benoit [Wed, 18 Mar 2026 16:13:49 +0000 (17:13 +0100)]
refactor(ocpp): make buildRequestPayload the authoritative builder for all commands
buildRequestPayload now calls the centralized builders for
TRANSACTION_EVENT (via buildTransactionEvent) and STATUS_NOTIFICATION
(via buildStatusNotificationRequest) in both OCPP versions, matching
the existing pattern for START_TRANSACTION/STOP_TRANSACTION in 1.6.
Callers pass minimal params (eventType, connectorId, status, etc.)
and buildRequestPayload constructs the complete spec-compliant payload.
sendTransactionEvent's offline path builds directly for queueing since
the queue stores pre-built payloads sent as-is on reconnect.
Jérôme Benoit [Wed, 18 Mar 2026 15:03:31 +0000 (16:03 +0100)]
refactor(ocpp): use buildStatusNotificationRequest helper in TriggerMessage handlers
Replace inline StatusNotification payload construction in both OCPP 1.6
and 2.0 TriggerMessage handlers with the centralized
buildStatusNotificationRequest helper. Export the helper from
OCPPServiceUtils to make it available.
This eliminates 5 inline duplications of the same payload structure
that risked diverging from the single source of truth.
Jérôme Benoit [Wed, 18 Mar 2026 14:49:05 +0000 (15:49 +0100)]
refactor(ocpp): harmonize buildRequestPayload switch structure across versions
Group passthrough cases into fall-through blocks in both OCPP 1.6 and
2.0 buildRequestPayload, giving both files a symmetric structure:
grouped passthroughs, heartbeat empty object, enrichment cases, default
error.
Jérôme Benoit [Wed, 18 Mar 2026 14:35:41 +0000 (15:35 +0100)]
fix(ocpp2): revert UI transaction handlers to simple passthrough
Remove handleUIStartTransaction and handleUIStopTransaction which
duplicated connector state management with incomplete initialization
(missing energy register, groupIdToken, StatusNotification, error
rollback) and bypassed authorization checks.
Restore handleTransactionEvent as a simple requestHandler passthrough,
consistent with all other broadcast channel command handlers.
Call OCPP20ServiceUtils.getTxUpdatedInterval() directly from
OCPP20IncomingRequestService instead of through a trivial private
wrapper that was left over from the centralization in PR #1734.
Jérôme Benoit [Wed, 18 Mar 2026 14:16:54 +0000 (15:16 +0100)]
refactor(ocpp2): consolidate dual-path request architecture into single path
Remove 8 unused dedicated request methods (requestFirmwareStatusNotification,
requestGet15118EVCertificate, requestGetCertificateStatus, etc.) that bypassed
buildRequestPayload via direct sendMessage calls. All production callers
already used requestHandler exclusively, making these methods dead code.
Move SignCertificate CSR generation logic into buildRequestPayload, making
it the authoritative payload construction layer — symmetric with OCPP 1.6.
This also adds isRequestCommandSupported check and AJV schema validation
to the SignCertificate flow that the dedicated method bypassed.
Refactor 6 test files to test through requestHandler (production path)
instead of the removed dedicated methods.
Jérôme Benoit [Wed, 18 Mar 2026 13:44:59 +0000 (14:44 +0100)]
refactor(ocpp2): centralize payload construction and eliminate duplication
Remove redundant timestamp injection from buildRequestPayload() for
StatusNotification and TransactionEvent — centralized builders already
provide timestamps and AJV schema validation catches any omission.
Extract buildStationInfoReportData() helper to deduplicate station info
report data construction between FullInventory and SummaryInventory in
buildReportData().
Normalize TriggerMessage Heartbeat to use OCPP20Constants.OCPP_RESPONSE_EMPTY
for consistency with buildRequestPayload().
- Update ConnectorStatus.transactionId to support both number (OCPP 1.6)
and string/UUID (OCPP 2.0.x)
Wave 1 complete.
* feat(ui): add UIClient transaction methods with OCPP version support
- Add transactionEvent() method for OCPP 2.0.x TransactionEvent requests
- Add isOCPP20x() static helper for version detection
- Add startTransactionForVersion() helper that routes to appropriate
method based on OCPP version (1.6 vs 2.0.x)
- Add stopTransactionForVersion() helper for version-aware stop
- Add comprehensive unit tests (16 tests passing)
Wave 2 complete.
* feat(ui): add version-aware StartTransaction form with OCPP 2.0.x support
- Modify StartTransaction.vue to detect OCPP version from station info
- Show connector ID for OCPP 1.6, EVSE ID input for OCPP 2.0.x
- Hide Authorize checkbox for OCPP 2.0.x stations (v-if)
- Use startTransactionForVersion() helper for version-aware API calls
- Add loading state while fetching station info
- Show appropriate form fields based on OCPP version
- All 16 tests passing
Wave 3 complete.
* fix(webui): use enums instead of string literals for OCPP 2.0.x types
- Replace 'ISO14443' string with OCPP20IdTokenEnumType.ISO14443
- Fix test mocks to use Protocol.UI and ResponseStatus.SUCCESS enums
- Export OCPP20IdTokenEnumType from types index
* style(webui): fix import ordering in UIClient.ts
* [autofix.ci] apply automated fixes
* chore: remove tsbuildinfo and add to gitignore
* fix(webui): address PR review comments
- Add ResponseStatus import and use enum instead of string
- Use UIClient.isOCPP20x() helper instead of manual comparison
- Remove redundant showAuthorize computed, use !isOCPP20x directly
- Separate error handling for authorize vs startTransaction
- Add validation for transactionId type in stopTransactionForVersion
* [autofix.ci] apply automated fixes
* chore: move tsbuildinfo gitignore to ui/web subdirectory
- Remove *.tsbuildinfo from root .gitignore
- Add *.tsbuildinfo to ui/web/.gitignore with proper comment
* ci(webui): add TypeScript type checking to CI
- Add vue-tsc dev dependency to ui/web
- Add typecheck script to package.json
- Add typecheck step to build-dashboard job in CI
* fix(webui): fix vue-tsc typecheck and improve Vue.js best practices
- Break recursive JsonObject/JsonType chain causing TS2589 in vue-tsc
- Fix CSConnector to use stopTransactionForVersion with ocppVersion prop
- Replace getCurrentInstance() anti-pattern with useToast()/useRouter()
- Make isOCPP20x a computed instead of manually-synced ref
- Deduplicate handleStartTransaction (remove ~30 lines of duplication)
- Add null guards for watch() on potentially undefined global refs
* [autofix.ci] apply automated fixes
* refactor(webui): align namespace with simulator and improve API design
- Merge startTransactionForVersion/stopTransactionForVersion into
startTransaction/stopTransaction with optional ocppVersion param
- Make transactionEvent private (implementation detail, not public API)
- Revert UITransactionEventPayload to OCPP20TransactionEventRequest
to match backend naming convention
- Pass ocppVersion via route param instead of re-fetching all stations
- Remove convertToBoolean no-op and loading state
- Flip negated v-if condition for SonarCloud compliance
- Factor test setup, remove duplicate coverage, add missing test case
- Net result: -90 lines, cleaner API surface
* [autofix.ci] apply automated fixes
* refactor(webui): move tests from __tests__ to tests/unit for consistency
Align test file location with existing project convention (tests/unit/)
instead of Jest-style __tests__ directory.
* feat(webui): integrate OCPP 2.0 EVSE 3-tier model across web UI
- Add OCPP20EVSEType matching spec EVSEType {id, connectorId?}
- Replace flat evseId with proper evse object in TransactionEventRequest
- CSData: preserve EVSE→Connector mapping instead of flattening
- CSConnector: display 'evseId/connectorId' for OCPP 2.0 stations
- Route: pass evseId and ocppVersion as query params (not sentinels)
- StartTransaction: read EVSE context from query, display EVSE/Connector
- UIClient.startTransaction: accept evseId, build spec-compliant evse object
- Tests: exhaustive decision tree coverage (18 tests, all branches)
* [autofix.ci] apply automated fixes
* refactor(webui): improve API elegance and fix cross-version concerns
- Refactor startTransaction/stopTransaction to use named options object
instead of positional params for better readability and grouping
- Fix ToggleButton ID collision: include evseId for multi-EVSE uniqueness
- Normalize idTag validation as cross-version concern (empty→undefined)
- Rename getConnectorStatuses→getConnectorEntries to match semantics
- Extract toggleButtonId as computed to avoid string duplication
* feat: entry-based serialization for EVSE/Connector/ATG data
Introduce ConnectorEntry, EvseEntry, and ATGStatusEntry types at the
UI serialization boundary to preserve Map keys (evseId, connectorId)
that were previously lost during Map-to-Array conversion.
Backend:
- Add buildConnectorEntries/buildEvseEntries using .entries()
- Keep buildConnectorsStatus/buildEvsesStatus unchanged for config persistence
- Remove EvseStatusWorkerType and OutputFormat enum
- ATG statuses serialized with connectorId at the UI boundary only
- ConnectorStatus and EvseStatus types remain pure (no identity fields)
Frontend:
- CSData uses explicit IDs from Entry types (no more index-based mapping)
- ATG status lookup by connectorId instead of array index
- Filter connector 0 in both OCPP 1.6 and 2.0 paths
- Both connectors and evses optional in ChargingStationData
* [autofix.ci] apply automated fixes
* fix(webui): use data presence instead of protocol version for EVSE display
EVSE/Connector display depends on whether the station has EVSEs (data),
not on the OCPP version (protocol). A station could use EVSEs regardless
of OCPP version.
* refactor: extract buildATGStatusEntries for consistent Entry builder pattern
Align ATG status serialization with ConnectorEntry/EvseEntry pattern:
dedicated builder function instead of inline transformation.
* refactor: harmonize Entry naming pattern across codebase
Rename ATGStatusEntry/buildATGStatusEntries to ATGEntry/buildATGEntries
to match ConnectorEntry/EvseEntry naming convention.
* refactor: define ATGConfiguration type and harmonize across workspaces
Extract inline ATG type into named ATGConfiguration interface, matching
the ATG*/Connector*/Evse* Entry pattern in both backend and frontend.
* test: add missing Entry builder tests and non-sequential ID coverage
- Add buildATGEntries tests (entries with IDs, non-sequential, no ATG, no status)
- Add non-sequential connector ID test for buildConnectorEntries (keys 0,3,7)
- Add non-sequential evseId/connectorId test for buildEvseEntries (3/2,5)
- These tests verify the core invariant of the Entry pattern: Map keys are
preserved regardless of their sequence
* refactor: organize tests by concern and replace throw with graceful failure
- Group backend tests: config persistence builders then UI Entry builders
- Replace throw Error in stopTransaction with ResponsePayload FAILURE
to avoid unhandled rejections in UI components
* docs: harmonize quality gate ordering (typecheck before lint) across codebase
Align CI workflows, READMEs, and copilot-instructions to consistently
run typecheck before lint in all three sub-projects.
* refactor(ocpp2): centralize TransactionEvent payload building
- requestStopTransaction delegates to sendTransactionEvent instead of
building the OCPP payload inline, eliminating duplication
- All TransactionEvent paths now converge through sendTransactionEvent
→ buildTransactionEvent as the single payload construction point
- handleTransactionEvent (UI) dispatches Started/Ended to proper flows
with connector state initialization and lifecycle management
- Remove unused imports (secondsToMilliseconds, Constants)
Jérôme Benoit [Tue, 17 Mar 2026 12:21:30 +0000 (13:21 +0100)]
fix(ocpp2): use convertToDate() for all incoming date fields in handlers
Replace raw new Date(string) and direct string comparisons with
convertToDate() for date fields in incoming OCPP 2.0 payloads:
- validateChargingProfile: validFrom/validTo comparisons
- validateChargingSchedule: startSchedule comparisons
- simulateFirmwareUpdateLifecycle: retrieveDateTime/installDateTime
- handleResponseHeartbeat: currentTime display
- OCPP20CertificateManager: cert.validFrom/validTo validation
Jérôme Benoit [Mon, 16 Mar 2026 23:02:44 +0000 (00:02 +0100)]
ci: move coverage/lint/typecheck/sonar pipeline from Node 22.x to 24.x
Node 22 --experimental-test-coverage has known bugs (nodejs/node#55510)
that cause false CI failures. Move all gated steps (coverage, lint,
typecheck, SonarCloud, dependency review) to Node 24.x. Node 22 remains
in the test matrix for regular pnpm test runs.
Jérôme Benoit [Mon, 16 Mar 2026 21:56:52 +0000 (22:56 +0100)]
fix(tests): add second flushMicrotasks for RequestStopTransaction listener
The async chain in requestStopTransaction traverses a dynamic import()
in checkConnectorStatusTransition (OCPPServiceUtils), which may resolve
after the first setImmediate on macOS + Node 22. A second flush ensures
the StatusNotification call completes before the assertion.
Jérôme Benoit [Mon, 16 Mar 2026 21:56:52 +0000 (22:56 +0100)]
fix(tests): add second flushMicrotasks for RequestStopTransaction listener
The async chain in requestStopTransaction traverses a dynamic import()
in checkConnectorStatusTransition (OCPPServiceUtils), which may resolve
after the first setImmediate on macOS + Node 22. A second flush ensures
the StatusNotification call completes before the assertion.
Jérôme Benoit [Mon, 16 Mar 2026 21:36:24 +0000 (22:36 +0100)]
test: harmonize event listener test pattern across OCPP command test files (#1730)
* test: harmonize event listener test pattern across OCPP command test files
Add event listener test sections to 7 OCPP incoming request command test
files (4 OCPP 1.6, 3 OCPP 2.0) following the reference pattern from
RequestStopTransaction, TriggerMessage, UpdateFirmware, and GetLog tests.
Each listener section contains: registration test, accepted-fires test,
rejected-not-fires test, and error-graceful test.
Also restructures CustomerInformation to wrap existing listener tests in a
properly named describe block, and adds createOCPP16ListenerStation helper
to OCPP16TestUtils.ts.
- Extract createOCPP20ListenerStation to OCPP20TestUtils.ts, removing
duplication between RequestStart and RequestStop test files
- Replace inline import('node:test').mock.fn with top-level mock import
in RemoteStartTransaction tests
- Remove redundant mock.reset() from 3 listener afterEach blocks —
standardCleanup() already calls mock.restoreAll()
- Replace all await Promise.resolve() with flushMicrotasks() across 5
OCPP 2.0 test files for more robust async side-effect flushing
* fix(tests): replace remaining await Promise.resolve() with flushMicrotasks()
8 occurrences in 5 files (3 OCPP16, 2 OCPP20) missed in the initial
review fix. Now all listener tests use flushMicrotasks() consistently.
- Fix import paths in 7 OCPP 2.0 test files: ../../../../tests/helpers/
→ ../../../helpers/ (correct relative path, consistent with 35 sibling
files in the same directory)
- Add eventType assertion in RequestStartTransaction listener test to
verify TransactionEvent(Started) per E02.FR.01
- Add flushMicrotasks() to RequestStopTransaction listener test for
consistent emit→flush→assert pattern
* refactor(tests): move mock.method to beforeEach and parameterize trigger tests
- Move duplicated mock.method calls into listener beforeEach blocks
in 5 files (UpdateFirmware, GetLog, GetBaseReport, CustomerInfo,
Firmware). Rejection tests override inline. Net -147 lines.
- Parameterize OCPP16 + OCPP20 TriggerMessage trigger-fires tests
using data-driven triggerCases arrays (already done in prior commit,
this commit includes the Firmware mock cleanup).
* style(tests): remove inconsistent separator comment in RemoteStopUnlock
The listener section had a '// ───' separator not used in any of
the other 10 test files. The await describe block is sufficient.
* docs(tests): add summary line to startTransaction JSDoc
* docs(tests): add event listener testing section to TEST_STYLE_GUIDE
Add §11 documenting the established listener test pattern: emit()
direct, flushMicrotasks(), listenerCount first, accepted/rejected/error
triad, mock.method in beforeEach. Add listener station factories to
§9 mock factories table and flushMicrotasks to §10 utility table.
* docs(tests): fix incorrect mock API in §11 code example
Replace Jest-style mockImplementation() with Node.js test runner
mock.method() override pattern matching actual test code.
* fix(tests): align all 112 test files with TEST_STYLE_GUIDE
- Move @file JSDoc headers above first import in 3 files (GetVariables,
MessageChannelUtils, Utils)
- Replace await Promise.resolve() with flushMicrotasks() in
AutomaticTransactionGenerator
- Replace 6 setTimeout(resolve, 50) hacks with flushMicrotasks() in
ChargingStationWorkerBroadcastChannel
- Document spec traceability prefix exception (G03.FR.xx, G04.INT.xx)
in TEST_STYLE_GUIDE §1 naming conventions
* docs(tests): align TEST_STYLE_GUIDE with actual test infrastructure
- Fix createMockChargingStation location (ChargingStationTestUtils →
helpers/StationHelpers)
- Add 7 widely-used factories to §9 table (10+ usages each)
- Remove unused expectAcceptedAuthorization from §9 auth table
- All locations verified against actual exports
- Core Principles: clarify assert.ok is for boolean/existence only
- §5: add missing quotes around glob in test script (matches package.json)
- §10: restore setupConnectorWithTransaction/clearConnectorTransaction
(27 usages across test suite — should not have been removed)
Jérôme Benoit [Mon, 16 Mar 2026 17:58:26 +0000 (18:58 +0100)]
refactor(tests): separate handler/listener tests and remove setTimeout hacks
Align TriggerMessage, UpdateFirmware, and GetLog test files with the
RequestStopTransaction reference pattern:
- Split into 'Handler validation' + 'event listener' describe groups
- Replace raw setTimeout delays with withMockTimers where needed
- Use emit() directly for listener tests (no wrapper helpers)
- Follows TEST_STYLE_GUIDE.md conventions
Jérôme Benoit [Mon, 16 Mar 2026 16:54:02 +0000 (17:54 +0100)]
refactor(ocpp): harmonize post-response event listener pattern across stacks
Move post-response logic from inline handler to event listeners:
- OCPP16 UpdateFirmware: fire-and-forget in handler → event listener
matching OCPP20 UpdateFirmware pattern
- OCPP20 RequestStopTransaction: await in handler → event listener
matching OCPP16 RemoteStopTransaction pattern
All commands with post-response behavior now use the same pattern:
handler validates and returns response, event listener performs
the async action after the response is sent to the CSMS.
Jérôme Benoit [Mon, 16 Mar 2026 16:28:14 +0000 (17:28 +0100)]
refactor(ocpp20): move TransactionEvent(Started) to event listener pattern
Move sendTransactionEvent(Started) and startTxUpdatedInterval from
inside handleRequestStartTransaction to a post-response event
listener in the constructor, matching the OCPP 1.6
RemoteStartTransaction pattern where the handler validates and
returns Accepted, then an event triggers the actual StartTransaction
message send.
Jérôme Benoit [Mon, 16 Mar 2026 15:54:52 +0000 (16:54 +0100)]
fix(ocpp): graceful OCPPError fallback instead of throw in buildMessageToSend
Replace defensive throw with OCPPError construction when messagePayload
is not an OCPPError instance. Uses InternalError code per OCPP-J §4.2.3.
Harmonizes with the ternary instanceof pattern used at L452/503 in the
same file for errorDetails extraction.
Jérôme Benoit [Mon, 16 Mar 2026 15:20:23 +0000 (16:20 +0100)]
fix(ocpp20): send actual MeterValues on trigger and block sessions during firmware update
- F06.FR.06: TriggerMessage for MeterValues now sends TransactionEvent(Updated)
with actual meter readings for each EVSE with active transactions instead
of hardcoded value: 0
- L01.FR.07: UpdateFirmware sets idle EVSE connectors to Unavailable when
AllowNewSessionsPendingFirmwareUpdate is absent or false, preventing new
sessions while waiting for active transactions to end before installing
Jérôme Benoit [Mon, 16 Mar 2026 14:58:06 +0000 (15:58 +0100)]
fix(ocpp20): send TransactionEvent(Started) on RequestStartTransaction (E02.FR.01)
The handler accepted remote start transactions without notifying the
CSMS via TransactionEvent. Per E02.FR.01, the CS SHALL send
TransactionEvent with eventType=Started when a transaction begins.
Also starts meter values interval for the new transaction.
Jérôme Benoit [Mon, 16 Mar 2026 13:09:55 +0000 (14:09 +0100)]
refactor(ocpp20): remove redundant lastFirmwareStatus from per-station state
lastFirmwareStatus in the WeakMap was a duplicate of
stationInfo.firmwareStatus (now written by sendFirmwareStatusNotification).
TriggerMessage now uses hasFirmwareUpdateInProgress() to determine
whether to send Idle or the current intermediate state, which also
fixes a bug where failure end states (DownloadFailed, InstallationFailed,
etc.) were returned instead of Idle.
Jérôme Benoit [Mon, 16 Mar 2026 12:36:04 +0000 (13:36 +0100)]
refactor: use union types consistently in common code and tests
- OCPPServiceUtils: use SampledValue union in generics, use
StatusNotificationRequest union in satisfies, remove unused
OCPP20StatusNotificationRequest import
- BroadcastChannel: use MeterValuesRequest/Response unions
- OCPPServiceUtils-validation.test: OCPP16MessageTrigger → MessageTrigger,
remove redundant casts
Jérôme Benoit [Mon, 16 Mar 2026 12:27:33 +0000 (13:27 +0100)]
refactor(ocpp): use union types in common code where possible
- OCPPServiceUtils: use SampledValue union in generic constraints
instead of inline OCPP16SampledValue | OCPP20SampledValue union;
use StatusNotificationRequest union in satisfies check
- BroadcastChannel: use MeterValuesRequest/Response unions instead
of OCPP20-specific types in version-dispatched handleMeterValues
Stack-specific types remain in version-switch branches where
TypeScript requires narrowed types for array push operations.
Jérôme Benoit [Mon, 16 Mar 2026 12:20:30 +0000 (13:20 +0100)]
refactor(tests): use union types instead of stack-specific types in common tests
Replace OCPP16RequestCommand → RequestCommand,
OCPP16IncomingRequestCommand → IncomingRequestCommand, and
OCPP20RequestCommand → RequestCommand in test files outside
stack-specific directories. Remove redundant 'as Type' casts
that were bridging the gap.
Jérôme Benoit [Mon, 16 Mar 2026 12:11:41 +0000 (13:11 +0100)]
refactor(ocpp20): prefix OperationalStatusEnumType with OCPP20
Last unprefixed OCPP 2.0 type participating in a common union.
OperationalStatusEnumType → OCPP20OperationalStatusEnumType
matching the OCPP16AvailabilityType counterpart in the
AvailabilityType union.
Jérôme Benoit [Mon, 16 Mar 2026 12:00:33 +0000 (13:00 +0100)]
refactor(ocpp20): prefix FirmwareStatusEnumType, extend FirmwareStatus union
- Rename FirmwareStatusEnumType → OCPP20FirmwareStatusEnumType per
existing OCPP16FirmwareStatus naming convention
- Extend FirmwareStatus union to include all OCPP 2.0.1 firmware states
- Extend FirmwareStatusNotificationRequest to union both versions
- Write firmwareStatus to stationInfo in sendFirmwareStatusNotification
fixing hasFirmwareUpdateInProgress which was always returning false
- Stack-specific code uses OCPP20FirmwareStatusEnumType, common code
uses generic FirmwareStatus union
Jérôme Benoit [Mon, 16 Mar 2026 00:20:36 +0000 (01:20 +0100)]
fix(ocpp16): reject float values for integer configuration keys per spec §5.3
Replace convertToInt (parseInt — silently truncates 3.7 to 3) with
Number.isInteger check. The spec §9.1 defines these keys as Type:
integer, and §5.3 requires Rejected for values that do not conform
to the expected format.