flushQueuedTransactionMessages,
OCPP20ServiceUtils,
OCPPAuthServiceFactory,
+ OCPPConstants,
type OCPPIncomingRequestService,
type OCPPRequestService,
sendAndSetConnectorStatus,
// eslint-disable-next-line @typescript-eslint/no-base-to-string
{ rawMessage: typeof data === 'string' ? data : data.toString() }
),
- Constants.UNKNOWN_OCPP_COMMAND
+ OCPPConstants.UNKNOWN_OCPP_COMMAND
)
.catch((sendError: unknown) => {
logger.error(
if (!(error instanceof OCPPError)) {
logger.warn(
`${this.logPrefix()} Error thrown at incoming OCPP command ${
- commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
+ commandName ?? requestCommandName ?? OCPPConstants.UNKNOWN_OCPP_COMMAND
// eslint-disable-next-line @typescript-eslint/no-base-to-string
} message '${data.toString()}' handling is not an OCPPError:`,
error
}
logger.error(
`${this.logPrefix()} Incoming OCPP command '${
- commandName ?? requestCommandName ?? Constants.UNKNOWN_OCPP_COMMAND
+ commandName ?? requestCommandName ?? OCPPConstants.UNKNOWN_OCPP_COMMAND
// eslint-disable-next-line @typescript-eslint/no-base-to-string
}' message '${data.toString()}'${
this.requests.has(messageId)
) {
++this.wsConnectionRetryCount
const reconnectDelay = this.getReconnectDelay()
- const reconnectDelayWithdraw = 1000
const reconnectTimeout =
- reconnectDelay - reconnectDelayWithdraw > 0 ? reconnectDelay - reconnectDelayWithdraw : 0
+ reconnectDelay - Constants.DEFAULT_WS_RECONNECT_TIMEOUT_OFFSET > 0
+ ? reconnectDelay - Constants.DEFAULT_WS_RECONNECT_TIMEOUT_OFFSET
+ : 0
logger.error(
`${this.logPrefix()} WebSocket connection retry in ${roundTo(
reconnectDelay,
},
// { from: OCPP16ChargePointStatus.Faulted, to: OCPP16ChargePointStatus.Faulted }
])
+
+ static readonly DEFAULT_IDTAG = '00000000'
}
OCPPVersion,
type RequestParams,
} from '../../../types/index.js'
-import { Constants, generateUUID, logger } from '../../../utils/index.js'
+import { generateUUID, logger } from '../../../utils/index.js'
import { OCPPRequestService } from '../OCPPRequestService.js'
import {
createPayloadValidatorMap,
switch (commandName) {
case OCPP16RequestCommand.AUTHORIZE:
return {
- idTag: Constants.DEFAULT_IDTAG,
+ idTag: OCPP16Constants.DEFAULT_IDTAG,
...commandParams,
} as unknown as Request
case OCPP16RequestCommand.BOOT_NOTIFICATION:
return OCPP16Constants.OCPP_REQUEST_EMPTY as unknown as Request
case OCPP16RequestCommand.START_TRANSACTION:
return {
- idTag: Constants.DEFAULT_IDTAG,
+ idTag: OCPP16Constants.DEFAULT_IDTAG,
meterStart: chargingStation.getEnergyActiveImportRegisterByConnectorId(
commandParams.connectorId as number,
true
static readonly DEFAULT_CONNECTION_URL = 'ws://localhost'
static readonly FIRMWARE_INSTALL_DELAY_MS = 5000
+
static readonly FIRMWARE_STATUS_DELAY_MS = 2000
static readonly FIRMWARE_VERIFY_DELAY_MS = 500
-
/**
* Default timeout in milliseconds for async OCPP 2.0 handler operations
* (e.g., certificate file I/O). Prevents handlers from hanging indefinitely.
static readonly LOG_UPLOAD_STEP_DELAY_MS = 1000
+ static readonly MAX_SECURITY_EVENT_SEND_ATTEMPTS = 3
+
+ static readonly MAX_VARIABLE_VALUE_LENGTH = 2500
+
static readonly RESET_DELAY_MS = 1000
static readonly RESET_IDLE_MONITOR_INTERVAL_MS = 5000
+ static readonly SECURITY_EVENT_RETRY_DELAY_MS = 5000
+
/**
* Set of MessageTriggerEnumType values that the charging station supports
* in the TriggerMessage handler. Used for validation and capability reporting.
})
.catch((error: unknown) => {
const retryCount = (event.retryCount ?? 0) + 1
- if (retryCount >= 3) {
+ if (retryCount >= OCPP20Constants.MAX_SECURITY_EVENT_SEND_ATTEMPTS) {
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.sendQueuedSecurityEvents: Discarding event '${event.type}' after ${retryCount.toString()} failed attempts`,
error
return
}
logger.error(
- `${chargingStation.logPrefix()} ${moduleName}.sendQueuedSecurityEvents: Failed to send queued event '${event.type}' (attempt ${retryCount.toString()}/3)`,
+ `${chargingStation.logPrefix()} ${moduleName}.sendQueuedSecurityEvents: Failed to send queued event '${event.type}' (attempt ${retryCount.toString()}/${OCPP20Constants.MAX_SECURITY_EVENT_SEND_ATTEMPTS.toString()})`,
error
)
queue.unshift({ ...event, retryCount })
stationState.isDrainingSecurityEvents = false
setTimeout(() => {
this.sendQueuedSecurityEvents(chargingStation)
- }, 5000)
+ }, OCPP20Constants.SECURITY_EVENT_RETRY_DELAY_MS)
})
}
drainNextEvent()
getConfigurationKey,
setConfigurationKeyValue,
} from '../../ConfigurationKeyUtils.js'
+import { OCPP20Constants } from './OCPP20Constants.js'
import {
applyPostProcess,
buildCaseInsensitiveCompositeKey,
variableValue = enforceReportingValueSize(variableValue, reportingValueSize)
}
- // Final absolute length enforcement (spec maxLength Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH)
- if (variableValue.length > Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH) {
- variableValue = variableValue.slice(0, Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH)
+ // Final absolute length enforcement (spec maxLength OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH)
+ if (variableValue.length > OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH) {
+ variableValue = variableValue.slice(0, OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH)
}
return {
attributeStatus: GetVariableStatusEnumType.Accepted,
// 1. Read ConfigurationValueSize and ValueSize if present and valid (>0).
// 2. If both valid, use the smaller positive value.
// 3. If only one valid, use that value.
- // 4. If neither valid/positive, fallback to spec maxLength (Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH).
- // 5. Enforce absolute upper cap of Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH (spec).
+ // 4. If neither valid/positive, fallback to spec maxLength (OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH).
+ // 5. Enforce absolute upper cap of OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH (spec).
// 6. Reject with TooLargeElement when attributeValue length strictly exceeds effectiveLimit.
if (resolvedAttributeType === AttributeEnumType.Actual) {
const configurationValueSizeKey = buildCaseInsensitiveCompositeKey(
effectiveLimit = effectiveLimit != null ? Math.min(effectiveLimit, valLimit) : valLimit
}
if (effectiveLimit == null || effectiveLimit <= 0) {
- effectiveLimit = Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH
+ effectiveLimit = OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH
}
- if (effectiveLimit > Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH) {
- effectiveLimit = Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH
+ if (effectiveLimit > OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH) {
+ effectiveLimit = OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH
}
if (attributeValue.length > effectiveLimit) {
return this.rejectSet(
)]: {
component: OCPP20ComponentName.DeviceDataCtrlr,
dataType: DataEnumType.integer,
- defaultValue: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString(),
+ defaultValue: OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH.toString(),
description: 'Maximum size allowed for configuration values when setting.',
- max: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH,
+ max: OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH,
maxLength: 5,
min: 1,
mutability: MutabilityEnumType.ReadOnly,
)]: {
component: OCPP20ComponentName.DeviceDataCtrlr,
dataType: DataEnumType.integer,
- defaultValue: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString(),
+ defaultValue: OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH.toString(),
description: 'Maximum size of reported values.',
- max: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH,
+ max: OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH,
maxLength: 5,
min: 1,
mutability: MutabilityEnumType.ReadOnly,
[buildRegistryKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20OptionalVariableName.ValueSize)]: {
component: OCPP20ComponentName.DeviceDataCtrlr,
dataType: DataEnumType.integer,
- defaultValue: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString(),
+ defaultValue: OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH.toString(),
description: 'Maximum size for any stored or reported value.',
- max: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH,
+ max: OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH,
maxLength: 5,
min: 1,
mutability: MutabilityEnumType.ReadOnly,
ConfigurationStatus,
DataTransferStatus,
GenericStatus,
+ type IncomingRequestCommand,
MeterValueMeasurand,
+ type RequestCommand,
ReservationStatus,
TriggerMessageStatus,
UnlockStatus,
static readonly OCPP_WEBSOCKET_TIMEOUT = 60000 // Ms
+ static readonly UNKNOWN_OCPP_COMMAND = 'unknown OCPP command' as
+ | IncomingRequestCommand
+ | RequestCommand
+
protected constructor () {
// This is intentional
}
export { OCPP20VariableManager } from './2.0/OCPP20VariableManager.js'
export { OCPPAuthServiceFactory } from './auth/index.js'
export { isIdTagAuthorized } from './IdTagAuthorization.js'
+export { OCPPConstants } from './OCPPConstants.js'
export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
export { OCPPRequestService } from './OCPPRequestService.js'
export { createOCPPServices } from './OCPPServiceFactory.js'
} from './UIServerSecurity.js'
import { getUsernameAndPasswordFromAuthorizationToken } from './UIServerUtils.js'
+const CLIENT_NOTIFICATION_DEBOUNCE_MS = 500
+
const moduleName = 'AbstractUIServer'
export abstract class AbstractUIServer {
this.clientNotificationDebounceTimer = setTimeout(() => {
this.notifyClients()
this.clientNotificationDebounceTimer = undefined
- }, 500)
+ }, CLIENT_NOTIFICATION_DEBOUNCE_MS)
}
public async sendInternalRequest (request: ProtocolRequest): Promise<ProtocolResponse> {
import type { ErrorType, IncomingRequestCommand, JsonType, RequestCommand } from '../types/index.js'
-import { Constants } from '../utils/index.js'
+import { OCPPConstants } from '../charging-station/ocpp/OCPPConstants.js'
import { BaseError } from './BaseError.js'
export class OCPPError extends BaseError {
super(message)
this.code = code
- this.command = command ?? Constants.UNKNOWN_OCPP_COMMAND
+ this.command = command ?? OCPPConstants.UNKNOWN_OCPP_COMMAND
this.details = details
}
}
type AutomaticTransactionGeneratorConfiguration,
type ChargingStationInfo,
CurrentType,
- type IncomingRequestCommand,
OCPPVersion,
- type RequestCommand,
VendorParametersKey,
} from '../types/index.js'
static readonly DEFAULT_HEARTBEAT_INTERVAL = 60000 // Ms
- static readonly DEFAULT_IDTAG = '00000000'
-
static readonly DEFAULT_LOG_STATISTICS_INTERVAL = 60 // Seconds
static readonly DEFAULT_MESSAGE_BUFFER_FLUSH_INTERVAL = 60000 // Ms
static readonly DEFAULT_WS_HANDSHAKE_TIMEOUT = 30 // Seconds
static readonly DEFAULT_WS_PING_INTERVAL = 30 // Seconds
static readonly DEFAULT_WS_RECONNECT_DELAY = 30 // Seconds
+ static readonly DEFAULT_WS_RECONNECT_TIMEOUT_OFFSET = 1000 // Ms
static readonly EMPTY_FROZEN_OBJECT = Object.freeze({})
// Values exceeding this limit cause Node.js to reset the delay to 1ms
static readonly MAX_SETINTERVAL_DELAY = 2147483647 // Ms
- static readonly OCPP_VALUE_ABSOLUTE_MAX_LENGTH = 2500
-
static readonly PERFORMANCE_RECORDS_TABLE = 'performance_records'
static readonly STOP_CHARGING_STATIONS_TIMEOUT = 60000 // Ms
static readonly STOP_MESSAGE_SEQUENCE_TIMEOUT = 30000 // Ms
-
- static readonly UNKNOWN_OCPP_COMMAND = 'unknown OCPP command' as
- | IncomingRequestCommand
- | RequestCommand
-
- private constructor () {
- // This is intentional
- }
}
// ReportingValueSize truncation test
await it('should truncate long SequenceList/MemberList values per ReportingValueSize', () => {
- // Ensure ReportingValueSize is at a small value (default is Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH). We will override configuration key if absent.
+ // Ensure ReportingValueSize is at a small value (default is OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH). We will override configuration key if absent.
const reportingSizeKey = buildConfigKey(
OCPP20ComponentName.DeviceDataCtrlr,
StandardParametersKey.ReportingValueSize
} from '../../../../src/types/index.js'
import { buildConfigKey } from '../../../../src/charging-station/index.js'
+import { OCPP20Constants } from '../../../../src/charging-station/ocpp/2.0/OCPP20Constants.js'
import { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
import {
OCPP20ComponentName.DeviceDataCtrlr,
OCPP20OptionalVariableName.ReportingValueSize
),
- Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString()
+ OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH.toString()
)
}
OCPP20ComponentName.DeviceDataCtrlr,
OCPP20OptionalVariableName.ConfigurationValueSize
),
- Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString()
+ OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH.toString()
)
upsertConfigurationKey(
chargingStation,
buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20OptionalVariableName.ValueSize),
- Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString()
+ OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH.toString()
)
}
buildConfigKey(OCPP20ComponentName.ChargingStation, OCPP20VendorVariableName.ConnectionUrl),
overLongValue
)
- // Set generous ValueSize (1500) and ReportingValueSize (1400) so only absolute cap applies (since both < Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH)
+ // Set generous ValueSize (1500) and ReportingValueSize (1400) so only absolute cap applies (since both < OCPP20Constants.MAX_VARIABLE_VALUE_LENGTH)
setValueSize(station, 1500)
setReportingValueSize(station, 1400)
const getRes = manager.getVariables(station, [
import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
+import { OCPPConstants } from '../../src/charging-station/ocpp/OCPPConstants.js'
import { BaseError } from '../../src/exception/BaseError.js'
import { OCPPError } from '../../src/exception/OCPPError.js'
import { ErrorType, RequestCommand } from '../../src/types/index.js'
-import { Constants } from '../../src/utils/index.js'
import { standardCleanup } from '../helpers/TestLifecycleHelpers.js'
await describe('OCPPError', async () => {
assert.strictEqual(ocppError.name, 'OCPPError')
assert.strictEqual(ocppError.message, '')
assert.strictEqual(ocppError.code, ErrorType.GENERIC_ERROR)
- assert.strictEqual(ocppError.command, Constants.UNKNOWN_OCPP_COMMAND)
+ assert.strictEqual(ocppError.command, OCPPConstants.UNKNOWN_OCPP_COMMAND)
assert.strictEqual(ocppError.details, undefined)
assert.ok(typeof ocppError.stack === 'string')
assert.notStrictEqual(ocppError.stack, '')