- :white_check_mark: GetDiagnostics
- :white_check_mark: DiagnosticsStatusNotification
-- :x: FirmwareStatusNotification
-- :x: UpdateFirmware
+- :white_check_mark: FirmwareStatusNotification
+- :white_check_mark: UpdateFirmware
#### Local Auth List Management Profile
type BootNotificationRequest,
type CachedRequest,
type ErrorCallback,
+ FirmwareStatus,
+ type FirmwareStatusNotificationRequest,
type HeartbeatRequest,
type IncomingRequest,
IncomingRequestCommand,
import {
type BootNotificationResponse,
type ErrorResponse,
+ type FirmwareStatusNotificationResponse,
type HeartbeatResponse,
type MeterValuesResponse,
RegistrationStatusEnumType,
});
this.getConnectorStatus(connectorId).status = chargePointStatus;
}
+ if (this.stationInfo?.firmwareStatus === FirmwareStatus.Installing) {
+ await this.ocppRequestService.requestHandler<
+ FirmwareStatusNotificationRequest,
+ FirmwareStatusNotificationResponse
+ >(this, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+ status: FirmwareStatus.Installed,
+ });
+ this.stationInfo.firmwareStatus = FirmwareStatus.Installed;
+ // TODO: bump firmware version
+ }
// Start the ATG
if (this.getAutomaticTransactionGeneratorConfigurationFromTemplate()?.enable === true) {
this.startAutomaticTransactionGenerator();
import {
type BootNotificationRequest,
type DataTransferRequest,
+ type DiagnosticsStatusNotificationRequest,
+ type FirmwareStatusNotificationRequest,
type HeartbeatRequest,
type MeterValuesRequest,
RequestCommand,
type BootNotificationResponse,
type DataTransferResponse,
DataTransferStatus,
+ type DiagnosticsStatusNotificationResponse,
+ type FirmwareStatusNotificationResponse,
type HeartbeatResponse,
type MeterValuesResponse,
RegistrationStatusEnumType,
| StatusNotificationResponse
| HeartbeatResponse
| MeterValuesResponse
- | DataTransferResponse;
+ | DataTransferResponse
+ | DiagnosticsStatusNotificationResponse
+ | FirmwareStatusNotificationResponse;
type CommandHandler = (
requestPayload?: BroadcastChannelRequestPayload
DataTransferResponse
>(this.chargingStation, RequestCommand.DATA_TRANSFER, requestPayload),
],
+ [
+ BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
+ async (requestPayload?: BroadcastChannelRequestPayload) =>
+ this.chargingStation.ocppRequestService.requestHandler<
+ DiagnosticsStatusNotificationRequest,
+ DiagnosticsStatusNotificationResponse
+ >(this.chargingStation, RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, requestPayload),
+ ],
+ [
+ BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
+ async (requestPayload?: BroadcastChannelRequestPayload) =>
+ this.chargingStation.ocppRequestService.requestHandler<
+ FirmwareStatusNotificationRequest,
+ FirmwareStatusNotificationResponse
+ >(this.chargingStation, RequestCommand.FIRMWARE_STATUS_NOTIFICATION, requestPayload),
+ ],
]);
this.chargingStation = chargingStation;
this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
let commandResponse: CommandResponse | void;
try {
commandResponse = await this.commandHandler(command, requestPayload);
- if (commandResponse === undefined || commandResponse === null) {
+ if (
+ commandResponse === undefined ||
+ commandResponse === null ||
+ Utils.isEmptyObject(commandResponse as CommandResponse)
+ ) {
responsePayload = {
hashId: this.chargingStation.stationInfo.hashId,
status: ResponseStatus.SUCCESS,
type ChangeAvailabilityRequest,
type ChangeConfigurationRequest,
type ClearChargingProfileRequest,
- type DiagnosticsStatusNotificationRequest,
type GetConfigurationRequest,
type GetDiagnosticsRequest,
OCPP16AvailabilityType,
type OCPP16ClearCacheRequest,
type OCPP16DataTransferRequest,
OCPP16DataTransferVendorId,
+ type OCPP16DiagnosticsStatusNotificationRequest,
+ OCPP16FirmwareStatus,
+ type OCPP16FirmwareStatusNotificationRequest,
type OCPP16HeartbeatRequest,
OCPP16IncomingRequestCommand,
OCPP16MessageTrigger,
type ChangeAvailabilityResponse,
type ChangeConfigurationResponse,
type ClearChargingProfileResponse,
- type DiagnosticsStatusNotificationResponse,
type GetConfigurationResponse,
type GetDiagnosticsResponse,
type OCPP16BootNotificationResponse,
type OCPP16DataTransferResponse,
OCPP16DataTransferStatus,
+ type OCPP16DiagnosticsStatusNotificationResponse,
+ type OCPP16FirmwareStatusNotificationResponse,
type OCPP16HeartbeatResponse,
type OCPP16StatusNotificationResponse,
type OCPP16TriggerMessageResponse,
[OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
[OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
[OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)],
- // [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
+ [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
]);
this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
[
) {
return OCPPConstants.OCPP_RESPONSE_EMPTY;
}
- logger.debug(
- chargingStation.logPrefix() +
- ' ' +
- OCPP16IncomingRequestCommand.UPDATE_FIRMWARE +
- ' request received: %j',
- commandPayload
- );
+ const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
+ if (retrieveDate.getTime() <= Date.now()) {
+ this.asyncResource
+ .runInAsyncScope(
+ this.updateFirmware.bind(this) as (
+ this: OCPP16IncomingRequestService,
+ ...args: any[]
+ ) => Promise<void>,
+ this,
+ chargingStation
+ )
+ .catch(() => {
+ /* This is intentional */
+ });
+ } else {
+ setTimeout(() => {
+ this.updateFirmware(chargingStation).catch(() => {
+ /* Intentional */
+ });
+ }, retrieveDate.getTime() - Date.now());
+ }
+ return OCPPConstants.OCPP_RESPONSE_EMPTY;
+ }
+
+ private async updateFirmware(
+ chargingStation: ChargingStation,
+ minDelay = 15,
+ maxDelay = 30
+ ): Promise<void> {
+ chargingStation.stopAutomaticTransactionGenerator();
+ for (const connectorId of chargingStation.connectors.keys()) {
+ if (
+ connectorId > 0 &&
+ chargingStation.getConnectorStatus(connectorId).transactionStarted === false
+ ) {
+ await chargingStation.ocppRequestService.requestHandler<
+ OCPP16StatusNotificationRequest,
+ OCPP16StatusNotificationResponse
+ >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
+ connectorId,
+ status: OCPP16ChargePointStatus.UNAVAILABLE,
+ errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+ });
+ }
+ }
+ await chargingStation.ocppRequestService.requestHandler<
+ OCPP16FirmwareStatusNotificationRequest,
+ OCPP16FirmwareStatusNotificationResponse
+ >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+ status: OCPP16FirmwareStatus.Downloading,
+ });
+ chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
+ await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
+ await chargingStation.ocppRequestService.requestHandler<
+ OCPP16FirmwareStatusNotificationRequest,
+ OCPP16FirmwareStatusNotificationResponse
+ >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+ status: OCPP16FirmwareStatus.Downloaded,
+ });
+ chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
+ await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
+ await chargingStation.ocppRequestService.requestHandler<
+ OCPP16FirmwareStatusNotificationRequest,
+ OCPP16FirmwareStatusNotificationResponse
+ >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+ status: OCPP16FirmwareStatus.Installing,
+ });
+ chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
+ await Utils.sleep(Utils.getRandomInteger(minDelay, maxDelay) * 1000);
+ await chargingStation.reset();
}
private async handleRequestGetDiagnostics(
) {
return OCPPConstants.OCPP_RESPONSE_EMPTY;
}
- logger.debug(
- chargingStation.logPrefix() +
- ' ' +
- OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
- ' request received: %j',
- commandPayload
- );
const uri = new URL(commandPayload.location);
if (uri.protocol.startsWith('ftp:')) {
let ftpClient: Client;
} bytes transferred from diagnostics archive ${info.name}`
);
await chargingStation.ocppRequestService.requestHandler<
- DiagnosticsStatusNotificationRequest,
- DiagnosticsStatusNotificationResponse
+ OCPP16DiagnosticsStatusNotificationRequest,
+ OCPP16DiagnosticsStatusNotificationResponse
>(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
status: OCPP16DiagnosticsStatus.Uploading,
});
);
if (uploadResponse.code === 226) {
await chargingStation.ocppRequestService.requestHandler<
- DiagnosticsStatusNotificationRequest,
- DiagnosticsStatusNotificationResponse
+ OCPP16DiagnosticsStatusNotificationRequest,
+ OCPP16DiagnosticsStatusNotificationResponse
>(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
status: OCPP16DiagnosticsStatus.Uploaded,
});
);
} catch (error) {
await chargingStation.ocppRequestService.requestHandler<
- DiagnosticsStatusNotificationRequest,
- DiagnosticsStatusNotificationResponse
+ OCPP16DiagnosticsStatusNotificationRequest,
+ OCPP16DiagnosticsStatusNotificationResponse
>(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
status: OCPP16DiagnosticsStatus.UploadFailed,
});
} to transfer the diagnostic logs archive`
);
await chargingStation.ocppRequestService.requestHandler<
- DiagnosticsStatusNotificationRequest,
- DiagnosticsStatusNotificationResponse
+ OCPP16DiagnosticsStatusNotificationRequest,
+ OCPP16DiagnosticsStatusNotificationResponse
>(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
status: OCPP16DiagnosticsStatus.UploadFailed,
});
import type { JsonObject, JsonType } from '../../../types/JsonType';
import type { OCPP16MeterValuesRequest } from '../../../types/ocpp/1.6/MeterValues';
import {
- type DiagnosticsStatusNotificationRequest,
type OCPP16BootNotificationRequest,
type OCPP16DataTransferRequest,
+ type OCPP16DiagnosticsStatusNotificationRequest,
type OCPP16HeartbeatRequest,
OCPP16RequestCommand,
type OCPP16StatusNotificationRequest,
+ type OCPP16UpdateFirmwareRequest,
} from '../../../types/ocpp/1.6/Requests';
import type {
OCPP16AuthorizeRequest,
),
'utf8'
)
- ) as JSONSchemaType<DiagnosticsStatusNotificationRequest>,
+ ) as JSONSchemaType<OCPP16DiagnosticsStatusNotificationRequest>,
],
[
OCPP16RequestCommand.HEARTBEAT,
)
) as JSONSchemaType<OCPP16DataTransferRequest>,
],
+ [
+ OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/FirmwareStatusNotification.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16UpdateFirmwareRequest>,
+ ],
]);
this.buildRequestPayload.bind(this);
}
} as unknown as Request;
case OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION:
return {
- status: commandParams?.diagnosticsStatus,
+ status: commandParams?.status,
} as unknown as Request;
case OCPP16RequestCommand.HEARTBEAT:
return {} as unknown as Request;
} as unknown as Request;
case OCPP16RequestCommand.DATA_TRANSFER:
return commandParams as unknown as Request;
+ case OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION:
+ return {
+ status: commandParams?.status,
+ } as unknown as Request;
default:
// OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
throw new OCPPError(
ChangeAvailabilityResponse,
ChangeConfigurationResponse,
ClearChargingProfileResponse,
- DiagnosticsStatusNotificationResponse,
GetConfigurationResponse,
GetDiagnosticsResponse,
OCPP16BootNotificationResponse,
OCPP16DataTransferResponse,
+ OCPP16DiagnosticsStatusNotificationResponse,
+ OCPP16FirmwareStatusNotificationResponse,
OCPP16HeartbeatResponse,
OCPP16StatusNotificationResponse,
OCPP16TriggerMessageResponse,
[OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this)],
[OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
[OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler.bind(this)],
+ [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
]);
this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([
[
),
'utf8'
)
- ) as JSONSchemaType<DiagnosticsStatusNotificationResponse>,
+ ) as JSONSchemaType<OCPP16DiagnosticsStatusNotificationResponse>,
],
[
OCPP16RequestCommand.DATA_TRANSFER,
)
) as JSONSchemaType<OCPP16DataTransferResponse>,
],
+ [
+ OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+ JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16FirmwareStatusNotificationResponse>,
+ ],
]);
this.jsonIncomingRequestResponseSchemas = new Map([
[
[ProcedureName.HEARTBEAT]: BroadcastChannelProcedureName.HEARTBEAT,
[ProcedureName.METER_VALUES]: BroadcastChannelProcedureName.METER_VALUES,
[ProcedureName.DATA_TRANSFER]: BroadcastChannelProcedureName.DATA_TRANSFER,
+ [ProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION]:
+ BroadcastChannelProcedureName.DIAGNOSTICS_STATUS_NOTIFICATION,
+ [ProcedureName.FIRMWARE_STATUS_NOTIFICATION]:
+ BroadcastChannelProcedureName.FIRMWARE_STATUS_NOTIFICATION,
};
protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>;
import type { ChargingStationTemplate } from './ChargingStationTemplate';
+import type { FirmwareStatus } from './ocpp/Requests';
export type ChargingStationInfo = Omit<
ChargingStationTemplate,
meterSerialNumber?: string;
maximumPower?: number; // Always in Watt
maximumAmperage?: number; // Always in Ampere
+ firmwareStatus?: FirmwareStatus;
};
export type ChargingStationInfoConfiguration = {
HEARTBEAT = 'heartbeat',
METER_VALUES = 'meterValues',
DATA_TRANSFER = 'dataTransfer',
+ DIAGNOSTICS_STATUS_NOTIFICATION = 'diagnosticsStatusNotification',
+ FIRMWARE_STATUS_NOTIFICATION = 'firmwareStatusNotification',
}
export interface RequestPayload extends JsonObject {
HEARTBEAT = 'heartbeat',
METER_VALUES = 'meterValues',
DATA_TRANSFER = 'dataTransfer',
+ DIAGNOSTICS_STATUS_NOTIFICATION = 'diagnosticsStatusNotification',
+ FIRMWARE_STATUS_NOTIFICATION = 'firmwareStatusNotification',
}
export interface BroadcastChannelRequestPayload extends RequestPayload {
export type OCPP16FirmwareStatusNotificationRequest = {
status: OCPP16FirmwareStatus;
-};
+} & JsonObject;
export interface GetDiagnosticsRequest extends JsonObject {
location: string;
stopTime?: Date;
}
-export interface DiagnosticsStatusNotificationRequest extends JsonObject {
+export interface OCPP16DiagnosticsStatusNotificationRequest extends JsonObject {
status: OCPP16DiagnosticsStatus;
}
export type OCPP16UpdateFirmwareResponse = EmptyObject;
+export type OCPP16FirmwareStatusNotificationResponse = EmptyObject;
+
export interface GetDiagnosticsResponse extends JsonObject {
fileName?: string;
}
-export type DiagnosticsStatusNotificationResponse = EmptyObject;
+export type OCPP16DiagnosticsStatusNotificationResponse = EmptyObject;
export enum OCPP16TriggerMessageStatus {
ACCEPTED = 'Accepted',
OCPP16AvailabilityType,
type OCPP16BootNotificationRequest,
type OCPP16DataTransferRequest,
+ type OCPP16DiagnosticsStatusNotificationRequest,
OCPP16FirmwareStatus,
+ type OCPP16FirmwareStatusNotificationRequest,
type OCPP16HeartbeatRequest,
OCPP16IncomingRequestCommand,
OCPP16MessageTrigger,
export type DataTransferRequest = OCPP16DataTransferRequest;
+export type DiagnosticsStatusNotificationRequest = OCPP16DiagnosticsStatusNotificationRequest;
+
+export type FirmwareStatusNotificationRequest = OCPP16FirmwareStatusNotificationRequest;
+
export type IncomingRequestHandler = (
chargingStation: ChargingStation,
commandPayload: JsonType
OCPP16ConfigurationStatus,
type OCPP16DataTransferResponse,
OCPP16DataTransferStatus,
+ type OCPP16DiagnosticsStatusNotificationResponse,
+ type OCPP16FirmwareStatusNotificationResponse,
type OCPP16HeartbeatResponse,
type OCPP16StatusNotificationResponse,
OCPP16TriggerMessageStatus,
export type DataTransferResponse = OCPP16DataTransferResponse;
+export type DiagnosticsStatusNotificationResponse = OCPP16DiagnosticsStatusNotificationResponse;
+
+export type FirmwareStatusNotificationResponse = OCPP16FirmwareStatusNotificationResponse;
+
export enum DefaultStatus {
ACCEPTED = 'Accepted',
REJECTED = 'Rejected',
TriggerMessage:
curl -d '[2,"123456789","TriggerMessage",{"requestedMessage":"BootNotification"}]' -H "Content-Type: application/json" -X POST http://localhost:3000/message
curl -d '[2,"123456789","TriggerMessage",{"requestedMessage":"HeartBeat"}]' -H "Content-Type: application/json" -X POST http://localhost:3000/message
+UpdateFirmware:
+ curl -d '[2,"123456789","UpdateFirmware",{"location":"ftp://localhost","retrieveDate":"2019-01-01T00:00:00.000Z"}]' -H "Content-Type: application/json" -X POST http://localhost:3000/message