]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp2): centralize payload construction and eliminate duplication
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 18 Mar 2026 13:44:59 +0000 (14:44 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 18 Mar 2026 13:44:59 +0000 (14:44 +0100)
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().

.serena/memories/suggested_commands.md
.serena/memories/task_completion_checklist.md
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts

index 13121cd05b0dfd94cff273bc045267e9414170c2..f93d266a1eef9c5af7d9ee99bda0f3c17971a3fc 100644 (file)
@@ -23,9 +23,9 @@ pnpm start:dev:debug            # Build + run with Node inspector
 
 ```bash
 pnpm format                     # Prettier + ESLint auto-fix (run this first)
+pnpm typecheck                  # TypeScript type check (no emit)
 pnpm lint                       # ESLint check only
 pnpm lint:fix                   # ESLint auto-fix only
-pnpm typecheck                  # TypeScript type check (no emit)
 ```
 
 ### Test
@@ -62,6 +62,7 @@ pnpm preview                    # Build + Vite preview
 
 ```bash
 pnpm format                     # Prettier + ESLint auto-fix
+pnpm typecheck                  # vue-tsc type checking (no emit)
 pnpm lint                       # ESLint check only
 pnpm lint:fix                   # ESLint auto-fix only
 ```
@@ -100,8 +101,8 @@ poetry run python server.py --command GetBaseReport --period 5
 
 ```bash
 poetry run task format           # Ruff auto-fix + format
-poetry run task lint             # Ruff check + format check
 poetry run task typecheck        # Mypy type check
+poetry run task lint             # Ruff check + format check
 ```
 
 ### Test
index 13648589d2cd8b068638d72fe00c72bbc165b083..e4b3db92350f847f6af9974803ce96dd7c5f41db 100644 (file)
@@ -7,6 +7,7 @@
 ```bash
 pnpm format
 pnpm typecheck
+pnpm lint
 pnpm build
 pnpm test
 ```
@@ -16,6 +17,7 @@ pnpm test
 ```bash
 cd ui/web
 pnpm format
+pnpm typecheck
 pnpm lint
 pnpm build
 pnpm test
@@ -26,8 +28,8 @@ pnpm test
 ```bash
 cd tests/ocpp-server
 poetry run task format
-poetry run task lint
 poetry run task typecheck
+poetry run task lint
 poetry run task test
 ```
 
index 0d479461d2f090abaa67814c822e038c7afa794f..a27832f48521f2e8c7f3871eee1d39804d903359 100644 (file)
@@ -159,6 +159,47 @@ import { getVariableMetadata, VARIABLE_REGISTRY } from './OCPP20VariableRegistry
 
 const moduleName = 'OCPP20IncomingRequestService'
 
+interface StationInfoReportField {
+  property: 'chargePointModel' | 'chargePointSerialNumber' | 'chargePointVendor' | 'firmwareVersion'
+  variable: OCPP20DeviceInfoVariableName
+}
+
+const STATION_INFO_FIELDS_FULL: readonly StationInfoReportField[] = Object.freeze([
+  { property: 'chargePointModel', variable: OCPP20DeviceInfoVariableName.Model },
+  { property: 'chargePointVendor', variable: OCPP20DeviceInfoVariableName.VendorName },
+  { property: 'chargePointSerialNumber', variable: OCPP20DeviceInfoVariableName.SerialNumber },
+  { property: 'firmwareVersion', variable: OCPP20DeviceInfoVariableName.FirmwareVersion },
+])
+
+const STATION_INFO_FIELDS_SUMMARY: readonly StationInfoReportField[] = Object.freeze([
+  { property: 'chargePointModel', variable: OCPP20DeviceInfoVariableName.Model },
+  { property: 'chargePointVendor', variable: OCPP20DeviceInfoVariableName.VendorName },
+  { property: 'firmwareVersion', variable: OCPP20DeviceInfoVariableName.FirmwareVersion },
+])
+
+const buildStationInfoReportData = (
+  chargingStation: ChargingStation,
+  fields: readonly StationInfoReportField[]
+): ReportDataType[] => {
+  const stationInfo = chargingStation.stationInfo
+  if (stationInfo == null) {
+    return []
+  }
+  const reportData: ReportDataType[] = []
+  for (const { property, variable } of fields) {
+    const value = stationInfo[property]
+    if (value != null) {
+      reportData.push({
+        component: { name: OCPP20ComponentName.ChargingStation },
+        variable: { name: variable },
+        variableAttribute: [{ type: AttributeEnumType.Actual, value }],
+        variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
+      })
+    }
+  }
+  return reportData
+}
+
 interface OCPP20StationState {
   activeFirmwareUpdateAbortController?: AbortController
   activeFirmwareUpdateRequestId?: number
@@ -442,7 +483,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
               .requestHandler<
                 OCPP20HeartbeatRequest,
                 OCPP20HeartbeatResponse
-              >(chargingStation, OCPP20RequestCommand.HEARTBEAT, {}, { skipBufferingOnError: true, triggerMessage: true })
+              >(chargingStation, OCPP20RequestCommand.HEARTBEAT, OCPP20Constants.OCPP_RESPONSE_EMPTY as OCPP20HeartbeatRequest, { skipBufferingOnError: true, triggerMessage: true })
               .catch(errorHandler)
             break
           case MessageTriggerEnumType.LogStatusNotification:
@@ -770,52 +811,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         break
 
       case ReportBaseEnumType.FullInventory:
-        if (chargingStation.stationInfo) {
-          const stationInfo = chargingStation.stationInfo
-          if (stationInfo.chargePointModel) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.Model },
-              variableAttribute: [
-                { type: AttributeEnumType.Actual, value: stationInfo.chargePointModel },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-          if (stationInfo.chargePointVendor) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.VendorName },
-              variableAttribute: [
-                { type: AttributeEnumType.Actual, value: stationInfo.chargePointVendor },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-          if (stationInfo.chargePointSerialNumber) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.SerialNumber },
-              variableAttribute: [
-                {
-                  type: AttributeEnumType.Actual,
-                  value: stationInfo.chargePointSerialNumber,
-                },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-          if (stationInfo.firmwareVersion) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.FirmwareVersion },
-              variableAttribute: [
-                { type: AttributeEnumType.Actual, value: stationInfo.firmwareVersion },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-        }
+        reportData.push(...buildStationInfoReportData(chargingStation, STATION_INFO_FIELDS_FULL))
 
         if (chargingStation.ocppConfiguration?.configurationKey) {
           for (const configKey of chargingStation.ocppConfiguration.configurationKey) {
@@ -987,39 +983,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         break
 
       case ReportBaseEnumType.SummaryInventory:
-        if (chargingStation.stationInfo) {
-          const stationInfo = chargingStation.stationInfo
-          if (stationInfo.chargePointModel) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.Model },
-              variableAttribute: [
-                { type: AttributeEnumType.Actual, value: stationInfo.chargePointModel },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-          if (stationInfo.chargePointVendor) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.VendorName },
-              variableAttribute: [
-                { type: AttributeEnumType.Actual, value: stationInfo.chargePointVendor },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-          if (stationInfo.firmwareVersion) {
-            reportData.push({
-              component: { name: OCPP20ComponentName.ChargingStation },
-              variable: { name: OCPP20DeviceInfoVariableName.FirmwareVersion },
-              variableAttribute: [
-                { type: AttributeEnumType.Actual, value: stationInfo.firmwareVersion },
-              ],
-              variableCharacteristics: { dataType: DataEnumType.string, supportsMonitoring: false },
-            })
-          }
-        }
+        reportData.push(...buildStationInfoReportData(chargingStation, STATION_INFO_FIELDS_SUMMARY))
 
         reportData.push({
           component: { name: OCPP20ComponentName.ChargingStation },
index 2bf1d99ee1d7d6f1cac46e28d85e9ff49db08f11..308e2740e9230197af74277fb03158c07c2b6cd0 100644 (file)
@@ -576,25 +576,14 @@ export class OCPP20RequestService extends OCPPRequestService {
       case OCPP20RequestCommand.LOG_STATUS_NOTIFICATION:
       case OCPP20RequestCommand.METER_VALUES:
       case OCPP20RequestCommand.NOTIFY_CUSTOMER_INFORMATION:
+      case OCPP20RequestCommand.NOTIFY_REPORT:
       case OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION:
       case OCPP20RequestCommand.SIGN_CERTIFICATE:
+      case OCPP20RequestCommand.STATUS_NOTIFICATION:
+      case OCPP20RequestCommand.TRANSACTION_EVENT:
         return commandParams as unknown as Request
       case OCPP20RequestCommand.HEARTBEAT:
         return OCPP20Constants.OCPP_RESPONSE_EMPTY as unknown as Request
-      case OCPP20RequestCommand.NOTIFY_REPORT:
-        return {
-          ...commandParams,
-        } as unknown as Request
-      case OCPP20RequestCommand.STATUS_NOTIFICATION:
-        return {
-          timestamp: new Date(),
-          ...commandParams,
-        } as unknown as Request
-      case OCPP20RequestCommand.TRANSACTION_EVENT:
-        return {
-          timestamp: new Date(),
-          ...commandParams,
-        } as unknown as Request
       default: {
         // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
         const errorMsg = `Unsupported OCPP command ${commandName as string} for payload building`