]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commit
fix(ocpp): implement OCPP 1.6 GetDiagnostics supersession via AbortController (#1991)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 9 Jul 2026 21:57:29 +0000 (23:57 +0200)
committerGitHub <noreply@github.com>
Thu, 9 Jul 2026 21:57:29 +0000 (23:57 +0200)
commit4d1d2d88addd7598b9979f7d215ec7a575dda866
tree48fb578770e0a2a9a6c514a2f2763eaebf2b5c5d
parent0ed90b1dc9bba68d12fa1dd95e652d2abda001aa
fix(ocpp): implement OCPP 1.6 GetDiagnostics supersession via AbortController (#1991)

Closes #1971

The entry guard added by PR #1987 at handleRequestGetDiagnostics
prevented concurrent FTP uploads racing on the same
${chargingStationId}_logs.tar.gz archive by silently dropping the
second GetDiagnostics.req with an empty GetDiagnostics.conf. That
mitigated the concrete race but was not the OCPP 1.6 supersession
semantic: the new request needed to abort the in-flight upload and
start a fresh one, with a terminal DiagnosticsStatusNotification
emitted for the superseded upload.

Refactor the entry guard from skip-second into abort-then-install:

- Add `activeDiagnosticsAbortController?: AbortController` to
  `OCPP16StationState` (alphabetical, above the existing fields).
- On supersession, the entry guard calls
  `stationState.activeDiagnosticsAbortController?.abort()` and
  installs a fresh controller. `diagnosticsUploadInProgress` stays
  `true` across the handoff so the §4.4 / §7.24 trigger cross-check
  never observes a false-idle window.
- `basic-ftp` v6 does not natively consume `AbortSignal`; the
  documented interruption path is `Client.close()`, which invokes
  `FtpContext.close()` and rejects the pending `uploadFrom` task
  with `User closed client during task`. The handler wires
  `signal.addEventListener('abort', () => ftpClient?.close(), { once: true })`.
- The existing `catch` at L1390 already emits
  `DiagnosticsStatusNotification(UploadFailed)` for any thrown error,
  so a close-triggered rejection flows through unchanged — no second
  emission needed. OCPP 1.6 `DiagnosticsStatusNotification.req` (§6.17)
  does not carry a `requestId`; the terminal for the superseded upload
  is uncorrelated by design, only its temporal position tells the CSMS
  which upload it belonged to.
- The `finally` clause identity-guards its cleanup
  (`if (stationState.activeDiagnosticsAbortController === abortController)`)
  so a superseded handler's late unwind cannot clobber the new
  lifecycle's state. Cleaner than the microtask-yield primitive
  sketched in the initial design because it removes the ordering
  dependency between the two handlers' async continuations.
- `resetStationState` aborts the in-flight controller before the base
  template deletes the WeakMap entry, following the cancel-before-delete
  ordering documented on `OCPP20IncomingRequestService.resetStationState`.

Mirrors the OCPP 2.0.1 log-upload supersession pattern (commits
29a8330fac6ed21864f384fabe29b4c8) but does not import
`AcceptedCanceled` — that response semantic is a 2.0.1 concept absent
from OCPP 1.6 `GetDiagnostics.conf` (§6.26).
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Firmware.test.ts
tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-GetDiagnostics.test.ts [new file with mode: 0644]