Key | Value(s) | Default Value | Value type | Description
--- | -------| --------------| ---------- | ------------
-supervisionUrls | | '' | string \| string[] | string or array of connection URIs to OCPP-J servers. It has priority over the global configuration parameter
+supervisionUrls | | '' | string\|string[] | string or array of connection URIs to OCPP-J servers. It has priority over the global configuration parameter
supervisionUser | | '' | string | basic HTTP authentication user to OCPP-J server
supervisionPassword | | '' | string | basic HTTP authentication password to OCPP-J server
supervisionUrlOcppConfiguration | true/false | false | boolean | Allow supervision URL configuration via a vendor OCPP parameter key
supervisionUrlOcppKey | | 'ConnectionUrl' | string | The vendor string that will be used as a vendor OCPP parameter key to set the supervision URL
ocppVersion | 1.6 | 1.6 | string | OCPP version
ocppProtocol | json | json | string | OCPP protocol
+ocppStrictCompliance | true/false | true | boolean | Strict adherence to the OCPP version and protocol specifications
wsOptions | | {} | ClientOptions & ClientRequestArgs | [ws](https://github.com/websockets/ws) and node.js [http](https://nodejs.org/api/http.html) clients options intersection
authorizationFile | | '' | string | RFID tags list file relative to src/assets path
baseName | | '' | string | base name to build charging stations name
enableStatistics | true/false | true | boolean | enable charging stations statistics
mayAuthorizeAtRemoteStart | true/false | true | boolean | always send authorize at remote start transaction when AuthorizeRemoteTxRequests is enabled
beginEndMeterValues | true/false | false | boolean | enable Transaction.{Begin,End} MeterValues
-outOfOrderEndMeterValues | true/false | false | boolean | send Transaction.End MeterValues out of order
+outOfOrderEndMeterValues | true/false | false | boolean | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter)
meteringPerTransaction | true/false | true | boolean | enable metering history on a per transaction basis
transactionDataMeterValues | true/false | false | boolean | enable transaction data MeterValues at stop transaction
mainVoltageMeterValues | true/false | true | boolean | include charging station main voltage MeterValues on three phased charging stations
return this?.wsConnection?.readyState === OPEN;
}
+ public getRegistrationStatus(): RegistrationStatus {
+ return this?.bootNotificationResponse?.status;
+ }
+
public isInUnknownState(): boolean {
return Utils.isNullOrUndefined(this?.bootNotificationResponse?.status);
}
return this.stationInfo.currentOutType ?? CurrentType.AC;
}
+ public getOcppStrictCompliance(): boolean {
+ return this.stationInfo.ocppStrictCompliance ?? false;
+ }
+
public getVoltageOut(): number | undefined {
const errMsg = `${this.logPrefix()} Unknown ${this.getCurrentOutType()} currentOutType in template file ${this.stationTemplateFile}, cannot define default voltage out`;
let defaultVoltageOut: number;
private async onOpen(): Promise<void> {
logger.info(`${this.logPrefix()} Connected to OCPP server through ${this.wsConnectionUrl.toString()}`);
- if (!this.isRegistered()) {
+ if (!this.isInAcceptedState()) {
// Send BootNotification
let registrationRetryCount = 0;
do {
this.bootNotificationResponse = await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
- if (!this.isRegistered()) {
- registrationRetryCount++;
+ if (!this.isInAcceptedState()) {
+ this.getRegistrationMaxRetries() !== -1 && registrationRetryCount++;
await Utils.sleep(this.bootNotificationResponse?.interval ? this.bootNotificationResponse.interval * 1000 : Constants.OCPP_DEFAULT_BOOT_NOTIFICATION_INTERVAL);
}
- } while (!this.isRegistered() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1));
+ } while (!this.isInAcceptedState() && (registrationRetryCount <= this.getRegistrationMaxRetries() || this.getRegistrationMaxRetries() === -1));
}
- if (this.isRegistered() && this.stationInfo.autoRegister) {
+ if (this.isInAcceptedState() && this.stationInfo.autoRegister) {
await this.ocppRequestService.sendBootNotification(this.bootNotificationRequest.chargePointModel,
this.bootNotificationRequest.chargePointVendor, this.bootNotificationRequest.chargeBoxSerialNumber, this.bootNotificationRequest.firmwareVersion);
}
if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) {
this.flushMessageBuffer();
}
- } else if (this.isInPendingState()) {
- // The central server shall issue a TriggerMessage to the charging station for the boot notification at the end of its configuration process
- while (!this.isInAcceptedState()) {
- await Utils.sleep(Constants.CHARGING_STATION_DEFAULT_START_SEQUENCE_DELAY);
- }
- await this.startMessageSequence();
- this.stopped && (this.stopped = false);
- if (this.wsConnectionRestarted && this.isWebSocketConnectionOpened()) {
- this.flushMessageBuffer();
- }
} else {
logger.error(`${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`);
}
public async handleRequest(messageId: string, commandName: OCPP16IncomingRequestCommand, commandPayload: JsonType): Promise<void> {
let result: JsonType;
- if (this.chargingStation.isInPendingState()
- && (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION || commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)) {
- throw new OCPPError(ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request payload ${JSON.stringify(commandPayload, null, 2)} while charging station is in pending state`, commandName);
+ if (this.chargingStation.getOcppStrictCompliance() && (this.chargingStation.isInPendingState()
+ && (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION || commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION))) {
+ throw new OCPPError(ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request payload ${JSON.stringify(commandPayload, null, 2)} while the charging station is in pending state on the central server`, commandName);
}
- // FIXME: Add template tunable for accepting incoming configuration requests while in unknown state
- if (this.chargingStation.isRegistered() || (this.chargingStation.isInUnknownState() && (commandName === OCPP16IncomingRequestCommand.GET_CONFIGURATION || commandName === OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION || commandName === OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY || commandName === OCPP16IncomingRequestCommand.TRIGGER_MESSAGE))) {
+ if (this.chargingStation.isRegistered() || (!this.chargingStation.getOcppStrictCompliance() && this.chargingStation.isInUnknownState())) {
if (this.incomingRequestHandlers.has(commandName)) {
try {
// Call the method to build the result
throw new OCPPError(ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented to handle request payload ${JSON.stringify(commandPayload, null, 2)}`, commandName);
}
} else {
- throw new OCPPError(ErrorType.SECURITY_ERROR, `The charging station is not registered on the central server. ${commandName} cannot be issued to handle request payload ${JSON.stringify(commandPayload, null, 2)}`, commandName);
+ throw new OCPPError(ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request payload ${JSON.stringify(commandPayload, null, 2)} while the charging station is not registered on the central server.`, commandName);
}
// Send the built result
await this.chargingStation.ocppRequestService.sendResult(messageId, result, commandName);
}
const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(this.chargingStation, connectorId, meterStop);
// FIXME: should be a callback, each OCPP commands implementation must do only one job
- (this.chargingStation.getBeginEndMeterValues() && !this.chargingStation.getOutOfOrderEndMeterValues())
+ (this.chargingStation.getBeginEndMeterValues() && this.chargingStation.getOcppStrictCompliance() && !this.chargingStation.getOutOfOrderEndMeterValues())
&& await this.sendTransactionEndMeterValues(connectorId, transactionId, transactionEndMeterValue);
const payload: StopTransactionRequest = {
transactionId,
import { HeartbeatRequest, OCPP16RequestCommand, StatusNotificationRequest } from '../../../types/ocpp/1.6/Requests';
import { HeartbeatResponse, OCPP16BootNotificationResponse, OCPP16RegistrationStatus, StatusNotificationResponse } from '../../../types/ocpp/1.6/Responses';
import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';
+import { RegistrationStatus, ResponseHandler } from '../../../types/ocpp/Responses';
import ChargingStation from '../../ChargingStation';
import { ErrorType } from '../../../types/ocpp/ErrorType';
import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
import OCPPError from '../../../exception/OCPPError';
import OCPPResponseService from '../OCPPResponseService';
-import { ResponseHandler } from '../../../types/ocpp/Responses';
import Utils from '../../../utils/Utils';
import logger from '../../../utils/Logger';
throw new OCPPError(ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented to handle request response payload ${JSON.stringify(payload, null, 2)}`, commandName);
}
} else {
- throw new OCPPError(ErrorType.SECURITY_ERROR, `The charging station is not registered on the central server. ${commandName} cannot be not issued to handle request response payload ${JSON.stringify(payload, null, 2)}`, commandName);
+ throw new OCPPError(ErrorType.SECURITY_ERROR, `${commandName} cannot be issued to handle request response payload ${JSON.stringify(payload, null, 2)} while the charging station is not registered on the central server. `, commandName);
}
}
this.chargingStation.addConfigurationKey(OCPP16StandardParametersKey.HeartBeatInterval, payload.interval.toString());
this.chargingStation.addConfigurationKey(OCPP16StandardParametersKey.HeartbeatInterval, payload.interval.toString(), { visible: false });
this.chargingStation.heartbeatSetInterval ? this.chargingStation.restartHeartbeat() : this.chargingStation.startHeartbeat();
- } else if (payload.status === OCPP16RegistrationStatus.PENDING) {
- logger.info(this.chargingStation.logPrefix() + ' Charging station in pending state on the central server');
+ }
+ if (Object.values(RegistrationStatus).includes(payload.status)) {
+ const logMsg = `${this.chargingStation.logPrefix()} Charging station in '${payload.status}' state on the central server`;
+ payload.status === OCPP16RegistrationStatus.REJECTED ? logger.warn(logMsg) : logger.info(logMsg);
} else {
- logger.warn(this.chargingStation.logPrefix() + ' Charging station rejected by the central server');
+ logger.error(this.chargingStation.logPrefix() + ' Charging station boot notification response received: %j with undefined registration status', payload);
}
}
return;
}
if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
- (this.chargingStation.getBeginEndMeterValues() && this.chargingStation.getOutOfOrderEndMeterValues())
+ (this.chargingStation.getBeginEndMeterValues() && !this.chargingStation.getOcppStrictCompliance() && this.chargingStation.getOutOfOrderEndMeterValues())
&& await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(transactionConnectorId, requestPayload.transactionId,
OCPP16ServiceUtils.buildTransactionEndMeterValue(this.chargingStation, transactionConnectorId, requestPayload.meterStop));
if (!this.chargingStation.isChargingStationAvailable() || !this.chargingStation.isConnectorAvailable(transactionConnectorId)) {
skipBufferingOnError: false,
triggerMessage: false
}): Promise<JsonType | OCPPError | string> {
- if (this.chargingStation.isInRejectedState() || (this.chargingStation.isInPendingState() && !params.triggerMessage)) {
- throw new OCPPError(ErrorType.SECURITY_ERROR, 'Cannot send command payload if the charging station is not in accepted state', commandName);
- // FIXME: Add template tunable for accepting incoming configuration requests while in unknown state
- } else if ((this.chargingStation.isInUnknownState() && (commandName === RequestCommand.BOOT_NOTIFICATION || commandName === IncomingRequestCommand.GET_CONFIGURATION || commandName === IncomingRequestCommand.CHANGE_CONFIGURATION || commandName === IncomingRequestCommand.CHANGE_AVAILABILITY || commandName === IncomingRequestCommand.TRIGGER_MESSAGE))
+ if ((this.chargingStation.isInUnknownState() && commandName === RequestCommand.BOOT_NOTIFICATION)
+ || (!this.chargingStation.getOcppStrictCompliance() && this.chargingStation.isInUnknownState())
|| this.chargingStation.isInAcceptedState() || (this.chargingStation.isInPendingState() && params.triggerMessage)) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
}), Constants.OCPP_WEBSOCKET_TIMEOUT, new OCPPError(ErrorType.GENERIC_ERROR, `Timeout for message id '${messageId}'`, commandName, messageData?.details as JsonType ?? {}), () => {
messageType === MessageType.CALL_MESSAGE && this.chargingStation.requests.delete(messageId);
});
- } else {
- throw new OCPPError(ErrorType.SECURITY_ERROR, 'Cannot send command payload if the charging station is in unknown state', commandName);
}
+ throw new OCPPError(ErrorType.SECURITY_ERROR, `Cannot send command ${commandName} payload when the charging station is in ${this.chargingStation.getRegistrationStatus()} state on the central server`, commandName);
}
protected handleRequestError(commandName: RequestCommand, error: Error): void {
supervisionPassword?: string;
ocppVersion?: OCPPVersion;
ocppProtocol?: OCPPProtocol;
+ ocppStrictCompliance?: boolean;
wsOptions?: ClientOptions & ClientRequestArgs;
authorizationFile?: string;
baseName: string;
static readonly OCPP_WEBSOCKET_TIMEOUT = 60000; // Ms
static readonly OCPP_TRIGGER_MESSAGE_DELAY = 2000; // Ms
- static readonly CHARGING_STATION_DEFAULT_START_SEQUENCE_DELAY = 60000; // Ms
static readonly CHARGING_STATION_DEFAULT_RESET_TIME = 60000; // Ms
static readonly CHARGING_STATION_ATG_INITIALIZATION_TIME = 1000; // Ms
static readonly CHARGING_STATION_ATG_DEFAULT_STOP_AFTER_HOURS = 0.25; // Hours