// 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,
MessageTrigger,
OCPP16AvailabilityType,
OCPP16BootNotificationRequest,
+ OCPP16ClearCacheRequest,
OCPP16HeartbeatRequest,
OCPP16IncomingRequestCommand,
OCPP16RequestCommand,
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,
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 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 { URL } from 'url';
-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<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>,
+ ],
+ ]);
}
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
)} 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 {
+ if (this.jsonSchemas.has(commandName)) {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ commandName,
+ this.jsonSchemas.get(commandName),
+ commandPayload
+ );
+ } else {
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: No JSON schema found for command ${commandName} PDU validation`
+ );
+ }
// Call the method to build the response
response = await this.incomingRequestHandlers.get(commandName)(
chargingStation,
// 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
>(chargingStation, OCPP16RequestCommand.METER_VALUES, {
connectorId,
transactionId,
- meterValue: transactionEndMeterValue,
+ meterValue: [transactionEndMeterValue],
});
}
const stopResponse = await chargingStation.ocppRequestService.requestHandler<
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,
clearCurrentCP = true;
}
if (clearCurrentCP) {
- connectorStatus.chargingProfiles[index] = {} as OCPP16ChargingProfile;
+ connectorStatus.chargingProfiles.splice(index, 1);
logger.debug(
`${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
commandPayload.connectorId
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;
>(chargingStation, OCPP16RequestCommand.METER_VALUES, {
connectorId,
transactionId,
- meterValue: transactionEndMeterValue,
+ meterValue: [transactionEndMeterValue],
});
}
await chargingStation.ocppRequestService.requestHandler<
return Constants.OCPP_RESPONSE_ACCEPTED;
}
}
- logger.info(
+ logger.warn(
chargingStation.logPrefix() +
' Trying to remote stop a non existing transaction ' +
transactionId.toString()
let ftpClient: Client;
try {
const logFiles = fs
- .readdirSync(path.resolve(__dirname, '../../../../'))
+ .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
.filter((file) => file.endsWith('.log'))
.map((file) => path.join('./', file));
const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
});
});
uploadResponse = await ftpClient.uploadFrom(
- path.join(path.resolve(__dirname, '../../../../'), diagnosticsArchive),
+ path.join(
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
+ diagnosticsArchive
+ ),
uri.pathname + diagnosticsArchive
);
if (uploadResponse.code === 226) {