X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2F1.6%2FOCPP16IncomingRequestService.ts;h=b785db03574aaf6afcd10dea5676533153d0a56e;hb=32de5a575189d226213641f5ee36004f8454cb50;hp=67967fd9718d7a4ac7bb38896b8537d8bf212013;hpb=0d8140bd623b67d6d7fddeda288fd945af0cb168;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 67967fd9..b785db03 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -1,5 +1,30 @@ // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. +import fs from 'fs'; +import path from 'path'; +import { URL, fileURLToPath } from 'url'; + +import { JSONSchemaType } from 'ajv'; +import { Client, FTPResponse } from 'basic-ftp'; +import tar from 'tar'; + +import OCPPError from '../../../exception/OCPPError'; +import { JsonObject, JsonType } from '../../../types/JsonType'; +import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode'; +import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus'; +import { + ChargingProfilePurposeType, + OCPP16ChargingProfile, +} from '../../../types/ocpp/1.6/ChargingProfile'; +import { + OCPP16StandardParametersKey, + OCPP16SupportedFeatureProfiles, +} from '../../../types/ocpp/1.6/Configuration'; +import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus'; +import { + OCPP16MeterValuesRequest, + OCPP16MeterValuesResponse, +} from '../../../types/ocpp/1.6/MeterValues'; import { ChangeAvailabilityRequest, ChangeConfigurationRequest, @@ -10,6 +35,7 @@ import { MessageTrigger, OCPP16AvailabilityType, OCPP16BootNotificationRequest, + OCPP16ClearCacheRequest, OCPP16HeartbeatRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, @@ -35,11 +61,6 @@ import { SetChargingProfileResponse, UnlockConnectorResponse, } from '../../../types/ocpp/1.6/Responses'; -import { - ChargingProfilePurposeType, - OCPP16ChargingProfile, -} from '../../../types/ocpp/1.6/ChargingProfile'; -import { Client, FTPResponse } from 'basic-ftp'; import { OCPP16AuthorizationStatus, OCPP16AuthorizeRequest, @@ -50,40 +71,24 @@ import { OCPP16StopTransactionRequest, OCPP16StopTransactionResponse, } from '../../../types/ocpp/1.6/Transaction'; -import { - OCPP16MeterValuesRequest, - OCPP16MeterValuesResponse, -} from '../../../types/ocpp/1.6/MeterValues'; -import { - OCPP16StandardParametersKey, - OCPP16SupportedFeatureProfiles, -} from '../../../types/ocpp/1.6/Configuration'; -import { URL, fileURLToPath } from 'url'; - -import type ChargingStation from '../../ChargingStation'; -import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils'; -import Constants from '../../../utils/Constants'; -import { DefaultResponse } from '../../../types/ocpp/Responses'; +import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration'; import { ErrorType } from '../../../types/ocpp/ErrorType'; import { IncomingRequestHandler } from '../../../types/ocpp/Requests'; -import { JsonType } from '../../../types/JsonType'; -import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode'; -import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus'; -import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus'; -import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; -import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration'; -import OCPPError from '../../../exception/OCPPError'; -import OCPPIncomingRequestService from '../OCPPIncomingRequestService'; -import Utils from '../../../utils/Utils'; -import fs from 'fs'; +import { DefaultResponse } from '../../../types/ocpp/Responses'; +import Constants from '../../../utils/Constants'; import logger from '../../../utils/Logger'; -import path from 'path'; -import tar from 'tar'; +import Utils from '../../../utils/Utils'; +import type ChargingStation from '../../ChargingStation'; +import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils'; +import { ChargingStationUtils } from '../../ChargingStationUtils'; +import OCPPIncomingRequestService from '../OCPPIncomingRequestService'; +import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; const moduleName = 'OCPP16IncomingRequestService'; export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService { private incomingRequestHandlers: Map; + private jsonSchemas: Map>; public constructor() { if (new.target?.name === moduleName) { @@ -125,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( @@ -142,20 +294,25 @@ 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 )} while the charging station is in pending state on the central server`, - commandName + commandName, + commandPayload ); } if ( 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, @@ -163,30 +320,32 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer ); } catch (error) { // Log - logger.error(chargingStation.logPrefix() + ' Handle request error: %j', error); + logger.error(chargingStation.logPrefix() + ' Handle 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 )}`, - commandName + commandName, + commandPayload ); } } 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 )} while the charging station is not registered on the central server.`, - commandName + commandName, + commandPayload ); } // Send the built response @@ -198,6 +357,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, @@ -251,7 +429,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer >(chargingStation, OCPP16RequestCommand.METER_VALUES, { connectorId, transactionId, - meterValue: transactionEndMeterValue, + meterValue: [transactionEndMeterValue], }); } const stopResponse = await chargingStation.ocppRequestService.requestHandler< @@ -333,23 +511,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, @@ -614,7 +775,11 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer if ( chargingStation.getLocalAuthListEnabled() && chargingStation.hasAuthorizedTags() && - chargingStation.authorizedTags.find((value) => value === commandPayload.idTag) + chargingStation.authorizedTagsCache + .getAuthorizedTags( + ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo) + ) + .find((value) => value === commandPayload.idTag) ) { connectorStatus.localAuthorizeIdTag = commandPayload.idTag; connectorStatus.idTagLocalAuthorized = true; @@ -834,7 +999,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer >(chargingStation, OCPP16RequestCommand.METER_VALUES, { connectorId, transactionId, - meterValue: transactionEndMeterValue, + meterValue: [transactionEndMeterValue], }); } await chargingStation.ocppRequestService.requestHandler< @@ -848,7 +1013,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer return Constants.OCPP_RESPONSE_ACCEPTED; } } - logger.info( + logger.warn( chargingStation.logPrefix() + ' Trying to remote stop a non existing transaction ' + transactionId.toString()