import { OCPPError } from '../../../exception/index.js'
import {
ConnectorEnumType,
+ ConnectorStatusEnum,
ErrorType,
GenericDeviceModelStatusEnumType,
type IncomingRequestHandler,
response: OCPP20GetBaseReportResponse
) => {
if (response.status === GenericDeviceModelStatusEnumType.Accepted) {
- const { reportBase, requestId } = request
- const reportData = this.buildReportData(chargingStation, reportBase)
- chargingStation.ocppRequestService
- .requestHandler<OCPP20NotifyReportRequest, OCPP20NotifyReportResponse>(
- chargingStation,
- OCPP20RequestCommand.NOTIFY_REPORT,
- {
- generatedAt: new Date(),
- reportData,
- requestId,
- seqNo: 0,
- tbc: false,
- }
- )
- .then(() => {
- logger.debug(
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
- `${chargingStation.logPrefix()} ${moduleName}.constructor: NotifyReport sent for requestId ${requestId} with ${reportData.length} report items`
- )
- return undefined
- })
- .catch((error: unknown) => {
+ this.sendNotifyReportRequest(chargingStation, request, response).catch(
+ (error: unknown) => {
logger.error(
`${chargingStation.logPrefix()} ${moduleName}.constructor: NotifyReport error:`,
error
)
- })
+ }
+ )
}
}
)
break
case ReportBaseEnumType.FullInventory:
- // Include all device model variables
- // 1. Station information
+ // 1. Charging Station information
if (chargingStation.stationInfo) {
const stationInfo = chargingStation.stationInfo
if (stationInfo.chargePointModel) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'Model' },
variableAttribute: [{ type: 'Actual', value: stationInfo.chargePointModel }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
}
if (stationInfo.chargePointVendor) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'VendorName' },
variableAttribute: [{ type: 'Actual', value: stationInfo.chargePointVendor }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
}
if (stationInfo.chargePointSerialNumber) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'SerialNumber' },
variableAttribute: [{ type: 'Actual', value: stationInfo.chargePointSerialNumber }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
}
if (stationInfo.firmwareVersion) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'FirmwareVersion' },
variableAttribute: [{ type: 'Actual', value: stationInfo.firmwareVersion }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
// 2. OCPP configuration
if (chargingStation.ocppConfiguration?.configurationKey) {
for (const configKey of chargingStation.ocppConfiguration.configurationKey) {
+ const variableAttributes = []
+ variableAttributes.push({ type: 'Actual', value: configKey.value })
+ if (!configKey.readonly) {
+ variableAttributes.push({ type: 'Target', value: undefined })
+ }
+
reportData.push({
component: { name: OCPP20ComponentName.OCPPCommCtrlr },
variable: { name: configKey.key },
- variableAttribute: [{ type: 'Actual', value: configKey.value }],
- variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
+ variableAttribute: variableAttributes,
+ variableCharacteristics: {
+ dataType: 'string',
+ supportsMonitoring: false,
+ },
})
}
}
reportData.push({
component: {
evse: { id: evseId },
- name: OCPP20ComponentName.DeviceDataCtrlr,
+ name: OCPP20ComponentName.EVSE,
},
variable: { name: 'AvailabilityState' },
variableAttribute: [{ type: 'Actual', value: evse.availability }],
for (const [connectorId, connector] of evse.connectors) {
reportData.push({
component: {
- evse: { connectorId: connectorId.toString(), id: evseId },
- name: OCPP20ComponentName.DeviceDataCtrlr,
+ evse: { connectorId, id: evseId },
+ name: OCPP20ComponentName.EVSE,
},
variable: { name: 'ConnectorType' },
variableAttribute: [
if (connectorId > 0) {
reportData.push({
component: {
- evse: { connectorId: connectorId.toString(), id: 1 },
- name: 'Connector',
+ evse: { connectorId, id: 1 },
+ name: OCPP20ComponentName.Connector,
},
variable: { name: 'ConnectorType' },
variableAttribute: [
break
case ReportBaseEnumType.SummaryInventory:
- // Include essential variables only
if (chargingStation.stationInfo) {
const stationInfo = chargingStation.stationInfo
if (stationInfo.chargePointModel) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'Model' },
variableAttribute: [{ type: 'Actual', value: stationInfo.chargePointModel }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
}
if (stationInfo.chargePointVendor) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'VendorName' },
variableAttribute: [{ type: 'Actual', value: stationInfo.chargePointVendor }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
}
if (stationInfo.firmwareVersion) {
reportData.push({
- component: { name: OCPP20ComponentName.DeviceDataCtrlr },
+ component: { name: OCPP20ComponentName.ChargingStation },
variable: { name: 'FirmwareVersion' },
variableAttribute: [{ type: 'Actual', value: stationInfo.firmwareVersion }],
variableCharacteristics: { dataType: 'string', supportsMonitoring: false },
})
}
}
+
+ reportData.push({
+ component: { name: OCPP20ComponentName.ChargingStation },
+ variable: { name: 'AvailabilityState' },
+ variableAttribute: [
+ {
+ type: 'Actual',
+ value: chargingStation.inAcceptedState() ? 'Available' : 'Unavailable',
+ },
+ ],
+ variableCharacteristics: { dataType: 'string', supportsMonitoring: true },
+ })
+
+ if (chargingStation.evses.size > 0) {
+ for (const [evseId, evse] of chargingStation.evses) {
+ reportData.push({
+ component: {
+ evse: { id: evseId },
+ name: OCPP20ComponentName.EVSE,
+ },
+ variable: { name: 'AvailabilityState' },
+ variableAttribute: [{ type: 'Actual', value: evse.availability }],
+ variableCharacteristics: { dataType: 'string', supportsMonitoring: true },
+ })
+ }
+ } else if (chargingStation.connectors.size > 0) {
+ // Fallback to connectors if no EVSE structure
+ for (const [connectorId, connector] of chargingStation.connectors) {
+ if (connectorId > 0) {
+ reportData.push({
+ component: {
+ evse: { connectorId, id: 1 },
+ name: OCPP20ComponentName.Connector,
+ },
+ variable: { name: 'AvailabilityState' },
+ variableAttribute: [
+ { type: 'Actual', value: connector.status ?? ConnectorStatusEnum.Unavailable },
+ ],
+ variableCharacteristics: { dataType: 'string', supportsMonitoring: true },
+ })
+ }
+ }
+ }
break
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${chargingStation.logPrefix()} ${moduleName}.handleRequestGetBaseReport: GetBaseReport request received with requestId ${commandPayload.requestId} and reportBase ${commandPayload.reportBase}`
)
- // Build report data to check if any data is available
+
+ const supportedReportBases = [
+ ReportBaseEnumType.ConfigurationInventory,
+ ReportBaseEnumType.FullInventory,
+ ReportBaseEnumType.SummaryInventory,
+ ]
+
+ if (!supportedReportBases.includes(commandPayload.reportBase)) {
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetBaseReport: Unsupported reportBase ${commandPayload.reportBase}`
+ )
+ return {
+ status: GenericDeviceModelStatusEnumType.NotSupported,
+ }
+ }
+
const reportData = this.buildReportData(chargingStation, commandPayload.reportBase)
if (reportData.length === 0) {
logger.info(
}
}
+ private async sendNotifyReportRequest (
+ chargingStation: ChargingStation,
+ request: OCPP20GetBaseReportRequest,
+ response: OCPP20GetBaseReportResponse
+ ): Promise<void> {
+ const { reportBase, requestId } = request
+ const reportData = this.buildReportData(chargingStation, reportBase)
+
+ // Fragment report data if needed (OCPP2 spec recommends max 100 items per message)
+ const maxItemsPerMessage = 100
+ const chunks = []
+ for (let i = 0; i < reportData.length; i += maxItemsPerMessage) {
+ chunks.push(reportData.slice(i, i + maxItemsPerMessage))
+ }
+
+ // Ensure we always send at least one message (even if empty)
+ if (chunks.length === 0) {
+ chunks.push([])
+ }
+
+ // Send fragmented NotifyReport messages
+ for (let seqNo = 0; seqNo < chunks.length; seqNo++) {
+ const isLastChunk = seqNo === chunks.length - 1
+ const chunk = chunks[seqNo]
+
+ await chargingStation.ocppRequestService.requestHandler<
+ OCPP20NotifyReportRequest,
+ OCPP20NotifyReportResponse
+ >(chargingStation, OCPP20RequestCommand.NOTIFY_REPORT, {
+ generatedAt: new Date(),
+ reportData: chunk,
+ requestId,
+ seqNo,
+ tbc: !isLastChunk,
+ })
+
+ logger.debug(
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ `${chargingStation.logPrefix()} ${moduleName}.sendNotifyReportRequest: NotifyReport sent seqNo=${seqNo} for requestId ${requestId} with ${chunk.length} report items (tbc=${!isLastChunk})`
+ )
+ }
+
+ logger.debug(
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ `${chargingStation.logPrefix()} ${moduleName}.sendNotifyReportRequest: Completed NotifyReport for requestId ${requestId} with ${reportData.length} total items in ${chunks.length} message(s)`
+ )
+ }
+
private validatePayload (
chargingStation: ChargingStation,
commandName: OCPP20IncomingRequestCommand,
}
export enum OCPP20ComponentName {
+ // Physical and Logical Components
+ AccessBarrier = 'AccessBarrier',
+ AcDcConverter = 'AcDcConverter',
+ AcPhaseSelector = 'AcPhaseSelector',
+ Actuator = 'Actuator',
+ AirCoolingSystem = 'AirCoolingSystem',
AlignedDataCtrlr = 'AlignedDataCtrlr',
+ AreaVentilation = 'AreaVentilation',
AuthCacheCtrlr = 'AuthCacheCtrlr',
AuthCtrlr = 'AuthCtrlr',
+ BayOccupancySensor = 'BayOccupancySensor',
+ BeaconLighting = 'BeaconLighting',
+ CableBreakawaySensor = 'CableBreakawaySensor',
+ CaseAccessSensor = 'CaseAccessSensor',
CHAdeMOCtrlr = 'CHAdeMOCtrlr',
+ ChargingStation = 'ChargingStation',
+ ChargingStatusIndicator = 'ChargingStatusIndicator',
ClockCtrlr = 'ClockCtrlr',
+ ConnectedEV = 'ConnectedEV',
+ Connector = 'Connector',
+ ConnectorHolsterRelease = 'ConnectorHolsterRelease',
+ ConnectorHolsterSensor = 'ConnectorHolsterSensor',
+ ConnectorPlugRetentionLock = 'ConnectorPlugRetentionLock',
+ ConnectorProtectionRelease = 'ConnectorProtectionRelease',
+ Controller = 'Controller',
+ ControlMetering = 'ControlMetering',
+ CPPWMController = 'CPPWMController',
CustomizationCtrlr = 'CustomizationCtrlr',
+ DataLink = 'DataLink',
DeviceDataCtrlr = 'DeviceDataCtrlr',
+ Display = 'Display',
DisplayMessageCtrlr = 'DisplayMessageCtrlr',
+ DistributionPanel = 'DistributionPanel',
+ ElectricalFeed = 'ElectricalFeed',
+ ELVSupply = 'ELVSupply',
+ EmergencyStopSensor = 'EmergencyStopSensor',
+ EnvironmentalLighting = 'EnvironmentalLighting',
+ EVRetentionLock = 'EVRetentionLock',
+ EVSE = 'EVSE',
+ ExternalTemperatureSensor = 'ExternalTemperatureSensor',
+ FiscalMetering = 'FiscalMetering',
+ FloodSensor = 'FloodSensor',
+ GroundIsolationProtection = 'GroundIsolationProtection',
+ Heater = 'Heater',
+ HumiditySensor = 'HumiditySensor',
ISO15118Ctrlr = 'ISO15118Ctrlr',
+ LightSensor = 'LightSensor',
+ LiquidCoolingSystem = 'LiquidCoolingSystem',
LocalAuthListCtrlr = 'LocalAuthListCtrlr',
+ LocalAvailabilitySensor = 'LocalAvailabilitySensor',
+ LocalController = 'LocalController',
+ LocalEnergyStorage = 'LocalEnergyStorage',
MonitoringCtrlr = 'MonitoringCtrlr',
OCPPCommCtrlr = 'OCPPCommCtrlr',
+ OverCurrentProtection = 'OverCurrentProtection',
+ OverCurrentProtectionRecloser = 'OverCurrentProtectionRecloser',
+ PowerContactor = 'PowerContactor',
+ RCD = 'RCD',
+ RCDRecloser = 'RCDRecloser',
+ RealTimeClock = 'RealTimeClock',
ReservationCtrlr = 'ReservationCtrlr',
SampledDataCtrlr = 'SampledDataCtrlr',
SecurityCtrlr = 'SecurityCtrlr',
+ ShockSensor = 'ShockSensor',
SmartChargingCtrlr = 'SmartChargingCtrlr',
+ SpacesCountSignage = 'SpacesCountSignage',
+ Switch = 'Switch',
TariffCostCtrlr = 'TariffCostCtrlr',
+ TemperatureSensor = 'TemperatureSensor',
+ TiltSensor = 'TiltSensor',
+ TokenReader = 'TokenReader',
TxCtrlr = 'TxCtrlr',
+ UIInput = 'UIInput',
+ UpstreamProtectionTrigger = 'UpstreamProtectionTrigger',
+ VehicleIdSensor = 'VehicleIdSensor',
}
export enum OCPP20ConnectorEnumType {
export type CertificateSignedStatusEnumType = GenericStatusEnumType
export interface ChargingStationType extends JsonObject {
+ customData?: CustomDataType
firmwareVersion?: string
model: string
modem?: ModemType
serialNumber?: string
vendorName: string
}
+
export interface ComponentType extends JsonObject {
evse?: EVSEType
instance?: string
name: OCPP20ComponentName | string
}
-export interface EVSEType extends JsonObject {
- connectorId?: string
- id: number
+export interface CustomDataType extends JsonObject {
+ vendorId: string
}
export type GenericStatusEnumType = GenericStatus
reasonCode: string
}
+interface EVSEType extends JsonObject {
+ connectorId?: number
+ id: number
+}
+
interface ModemType extends JsonObject {
+ customData?: CustomDataType
iccid?: string
imsi?: string
}