import path from 'path';
import { URL, fileURLToPath } from 'url';
+import type { JSONSchemaType } from 'ajv';
import { Client, FTPResponse } from 'basic-ftp';
import tar from 'tar';
import OCPPError from '../../../exception/OCPPError';
-import { JsonType } from '../../../types/JsonType';
+import type { JsonObject, JsonType } from '../../../types/JsonType';
import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
import {
OCPP16SupportedFeatureProfiles,
} from '../../../types/ocpp/1.6/Configuration';
import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
-import {
+import type {
OCPP16MeterValuesRequest,
OCPP16MeterValuesResponse,
} from '../../../types/ocpp/1.6/MeterValues';
MessageTrigger,
OCPP16AvailabilityType,
OCPP16BootNotificationRequest,
+ OCPP16ClearCacheRequest,
OCPP16HeartbeatRequest,
OCPP16IncomingRequestCommand,
OCPP16RequestCommand,
SetChargingProfileRequest,
UnlockConnectorRequest,
} from '../../../types/ocpp/1.6/Requests';
-import {
+import type {
ChangeAvailabilityResponse,
ChangeConfigurationResponse,
ClearChargingProfileResponse,
OCPP16StopTransactionRequest,
OCPP16StopTransactionResponse,
} from '../../../types/ocpp/1.6/Transaction';
-import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
+import type { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
import { ErrorType } from '../../../types/ocpp/ErrorType';
-import { IncomingRequestHandler } from '../../../types/ocpp/Requests';
-import { DefaultResponse } from '../../../types/ocpp/Responses';
+import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
+import type { DefaultResponse } from '../../../types/ocpp/Responses';
import Constants from '../../../utils/Constants';
import logger from '../../../utils/Logger';
import Utils from '../../../utils/Utils';
export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
+ private jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
public constructor() {
if (new.target?.name === moduleName) {
[OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
[OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
]);
+ this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
+ [
+ OCPP16IncomingRequestCommand.RESET,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/Reset.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ResetRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.CLEAR_CACHE,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16ClearCacheRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<UnlockConnectorRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<GetConfigurationRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ChangeConfigurationRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<GetDiagnosticsRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<SetChargingProfileRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ClearChargingProfileRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ChangeAvailabilityRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<RemoteStartTransactionRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<RemoteStopTransactionRequest>,
+ ],
+ [
+ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16TriggerMessageRequest>,
+ ],
+ ]);
+ this.validatePayload.bind(this);
}
public async incomingRequestHandler(
) {
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `${commandName} cannot be issued to handle request payload ${JSON.stringify(
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
commandPayload,
null,
2
chargingStation.isRegistered() ||
(!chargingStation.getOcppStrictCompliance() && chargingStation.isInUnknownState())
) {
- if (this.incomingRequestHandlers.has(commandName)) {
+ if (
+ this.incomingRequestHandlers.has(commandName) &&
+ ChargingStationUtils.isIncomingRequestCommandSupported(commandName, chargingStation)
+ ) {
try {
+ this.validatePayload(chargingStation, commandName, commandPayload);
// Call the method to build the response
response = await this.incomingRequestHandlers.get(commandName)(
chargingStation,
);
} catch (error) {
// Log
- logger.error(chargingStation.logPrefix() + ' Handle request error: %j', error);
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
+ error
+ );
throw error;
}
} else {
// Throw exception
throw new OCPPError(
ErrorType.NOT_IMPLEMENTED,
- `${commandName} is not implemented to handle request payload ${JSON.stringify(
+ `${commandName} is not implemented to handle request PDU ${JSON.stringify(
commandPayload,
null,
2
} else {
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `${commandName} cannot be issued to handle request payload ${JSON.stringify(
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
commandPayload,
null,
2
);
}
+ private validatePayload(
+ chargingStation: ChargingStation,
+ commandName: OCPP16IncomingRequestCommand,
+ commandPayload: JsonType
+ ): boolean {
+ if (this.jsonSchemas.has(commandName)) {
+ return this.validateIncomingRequestPayload(
+ chargingStation,
+ commandName,
+ this.jsonSchemas.get(commandName),
+ commandPayload
+ );
+ }
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
+ );
+ return false;
+ }
+
// Simulate charging station restart
private handleRequestReset(
chargingStation: ChargingStation,
OCPP16StopTransactionResponse
>(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, {
transactionId,
- meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
+ meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(
+ transactionId,
+ true
+ ),
idTag: chargingStation.getTransactionIdTag(transactionId),
reason: OCPP16StopTransactionReason.UNLOCK_COMMAND,
});
chargingStation: ChargingStation,
commandPayload: ChangeConfigurationRequest
): ChangeConfigurationResponse {
- // JSON request fields type sanity check
- if (!Utils.isString(commandPayload.key)) {
- logger.error(
- `${chargingStation.logPrefix()} ${
- OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
- } request key field is not a string:`,
- commandPayload
- );
- }
- if (!Utils.isString(commandPayload.value)) {
- logger.error(
- `${chargingStation.logPrefix()} ${
- OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
- } request value field is not a string:`,
- commandPayload
- );
- }
const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
chargingStation,
commandPayload.key,
const transactionConnectorId = commandPayload.connectorId;
const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
if (transactionConnectorId) {
+ const remoteStartTransactionLogMsg =
+ chargingStation.logPrefix() +
+ ' Transaction remotely STARTED on ' +
+ chargingStation.stationInfo.chargingStationId +
+ '#' +
+ transactionConnectorId.toString() +
+ " for idTag '" +
+ commandPayload.idTag +
+ "'";
await chargingStation.ocppRequestService.requestHandler<
OCPP16StatusNotificationRequest,
OCPP16StatusNotificationResponse
connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
connectorStatus.idTagLocalAuthorized = true;
authorized = true;
- } else if (chargingStation.getMayAuthorizeAtRemoteStart()) {
+ } else if (chargingStation.getMustAuthorizeAtRemoteStart()) {
connectorStatus.authorizeIdTag = commandPayload.idTag;
const authorizeResponse: OCPP16AuthorizeResponse =
await chargingStation.ocppRequestService.requestHandler<
})
).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
) {
- logger.debug(
- chargingStation.logPrefix() +
- ' Transaction remotely STARTED on ' +
- chargingStation.stationInfo.chargingStationId +
- '#' +
- transactionConnectorId.toString() +
- ' for idTag ' +
- commandPayload.idTag
- );
+ logger.debug(remoteStartTransactionLogMsg);
return Constants.OCPP_RESPONSE_ACCEPTED;
}
return this.notifyRemoteStartTransactionRejected(
})
).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
) {
- logger.debug(
- chargingStation.logPrefix() +
- ' Transaction remotely STARTED on ' +
- chargingStation.stationInfo.chargingStationId +
- '#' +
- transactionConnectorId.toString() +
- ' for idTag ' +
- commandPayload.idTag
- );
+ logger.debug(remoteStartTransactionLogMsg);
return Constants.OCPP_RESPONSE_ACCEPTED;
}
return this.notifyRemoteStartTransactionRejected(
chargingStation.logPrefix() +
' Remote starting transaction REJECTED on connector Id ' +
connectorId.toString() +
- ', idTag ' +
+ ", idTag '" +
idTag +
- ', availability ' +
+ "', availability '" +
chargingStation.getConnectorStatus(connectorId).availability +
- ', status ' +
- chargingStation.getConnectorStatus(connectorId).status
+ "', status '" +
+ chargingStation.getConnectorStatus(connectorId).status +
+ "'"
);
return Constants.OCPP_RESPONSE_REJECTED;
}
OCPP16StopTransactionResponse
>(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, {
transactionId,
- meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
+ meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(
+ transactionId,
+ true
+ ),
idTag: chargingStation.getTransactionIdTag(transactionId),
});
return Constants.OCPP_RESPONSE_ACCEPTED;
chargingStation,
OCPP16RequestCommand.BOOT_NOTIFICATION,
{
- chargePointModel: chargingStation.getBootNotificationRequest().chargePointModel,
- chargePointVendor: chargingStation.getBootNotificationRequest().chargePointVendor,
+ chargePointModel: chargingStation.bootNotificationRequest.chargePointModel,
+ chargePointVendor: chargingStation.bootNotificationRequest.chargePointVendor,
chargeBoxSerialNumber:
- chargingStation.getBootNotificationRequest().chargeBoxSerialNumber,
- firmwareVersion: chargingStation.getBootNotificationRequest().firmwareVersion,
+ chargingStation.bootNotificationRequest.chargeBoxSerialNumber,
+ firmwareVersion: chargingStation.bootNotificationRequest.firmwareVersion,
chargePointSerialNumber:
- chargingStation.getBootNotificationRequest().chargePointSerialNumber,
- iccid: chargingStation.getBootNotificationRequest().iccid,
- imsi: chargingStation.getBootNotificationRequest().imsi,
- meterSerialNumber: chargingStation.getBootNotificationRequest().meterSerialNumber,
- meterType: chargingStation.getBootNotificationRequest().meterType,
+ chargingStation.bootNotificationRequest.chargePointSerialNumber,
+ iccid: chargingStation.bootNotificationRequest.iccid,
+ imsi: chargingStation.bootNotificationRequest.imsi,
+ meterSerialNumber: chargingStation.bootNotificationRequest.meterSerialNumber,
+ meterType: chargingStation.bootNotificationRequest.meterType,
},
{ skipBufferingOnError: true, triggerMessage: true }
)