From: Jérôme Benoit Date: Wed, 18 Mar 2026 13:44:59 +0000 (+0100) Subject: refactor(ocpp2): centralize payload construction and eliminate duplication X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=285b9a3b2ca05509483c9ca9a21c2c1ed389b597;p=e-mobility-charging-stations-simulator.git 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(). --- diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md index 13121cd0..f93d266a 100644 --- a/.serena/memories/suggested_commands.md +++ b/.serena/memories/suggested_commands.md @@ -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 diff --git a/.serena/memories/task_completion_checklist.md b/.serena/memories/task_completion_checklist.md index 13648589..e4b3db92 100644 --- a/.serena/memories/task_completion_checklist.md +++ b/.serena/memories/task_completion_checklist.md @@ -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 ``` diff --git a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts index 0d479461..a27832f4 100644 --- a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts @@ -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 }, diff --git a/src/charging-station/ocpp/2.0/OCPP20RequestService.ts b/src/charging-station/ocpp/2.0/OCPP20RequestService.ts index 2bf1d99e..308e2740 100644 --- a/src/charging-station/ocpp/2.0/OCPP20RequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20RequestService.ts @@ -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`