X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16IncomingRequestService.ts;h=0589da01c4d6fc3452009275ac1e64a4bffe0b3a;hb=07989fad0a792547969c7a544d0093f4cb6338d3;hp=172aa8dd4a55ec95b1ccf845bcc668f848841b01;hpb=65554cc3fc240ee17d57c57ec60a2a0da4d757ba;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 172aa8dd..0589da01 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -4,11 +4,12 @@ import fs from 'fs'; 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 { @@ -20,7 +21,7 @@ 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'; @@ -34,6 +35,7 @@ import { MessageTrigger, OCPP16AvailabilityType, OCPP16BootNotificationRequest, + OCPP16ClearCacheRequest, OCPP16HeartbeatRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, @@ -45,7 +47,7 @@ import { SetChargingProfileRequest, UnlockConnectorRequest, } from '../../../types/ocpp/1.6/Requests'; -import { +import type { ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, @@ -69,10 +71,10 @@ import { 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'; @@ -86,6 +88,7 @@ const moduleName = 'OCPP16IncomingRequestService'; export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService { private incomingRequestHandlers: Map; + private jsonSchemas: Map>; public constructor() { if (new.target?.name === moduleName) { @@ -127,6 +130,153 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)], [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)], ]); + this.jsonSchemas = new Map>([ + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + [ + 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, + ], + ]); + this.validatePayload.bind(this); } public async incomingRequestHandler( @@ -144,7 +294,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer ) { 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 @@ -159,9 +309,10 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer ) { if ( this.incomingRequestHandlers.has(commandName) && - ChargingStationUtils.isCommandSupported(commandName, chargingStation.stationInfo) + ChargingStationUtils.isIncomingRequestCommandSupported(commandName, chargingStation) ) { try { + this.validatePayload(chargingStation, commandName, commandPayload); // Call the method to build the response response = await this.incomingRequestHandlers.get(commandName)( chargingStation, @@ -169,14 +320,17 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer ); } 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 @@ -188,7 +342,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer } 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 @@ -206,6 +360,25 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer ); } + 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, @@ -267,7 +440,10 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer OCPP16StopTransactionResponse >(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, { transactionId, - meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId), + meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId( + transactionId, + true + ), idTag: chargingStation.getTransactionIdTag(transactionId), reason: OCPP16StopTransactionReason.UNLOCK_COMMAND, }); @@ -341,23 +517,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer 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, @@ -606,6 +765,15 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer 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 @@ -631,7 +799,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer 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< @@ -669,15 +837,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer }) ).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( @@ -718,15 +878,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer }) ).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( @@ -776,12 +928,13 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer 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; } @@ -854,7 +1007,10 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer OCPP16StopTransactionResponse >(chargingStation, OCPP16RequestCommand.STOP_TRANSACTION, { transactionId, - meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId), + meterStop: chargingStation.getEnergyActiveImportRegisterByTransactionId( + transactionId, + true + ), idTag: chargingStation.getTransactionIdTag(transactionId), }); return Constants.OCPP_RESPONSE_ACCEPTED; @@ -1019,17 +1175,17 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer 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 } )