this.templateFile
}`;
logger.error(errMsg);
- throw new Error(errMsg);
+ throw new BaseError(errMsg);
}
private initialize(): void {
break;
default:
logger.error(errMsg);
- throw new Error(errMsg);
+ throw new BaseError(errMsg);
}
return defaultVoltageOut;
}
if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
const errorMsg = `${chargingStation.logPrefix()} Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
logger.error(errorMsg);
- throw new Error(errorMsg);
+ throw new BaseError(errorMsg);
}
logger.debug(
`${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
+import BaseError from '../exception/BaseError';
import { RequestCommand } from '../types/ocpp/Requests';
import {
+ AuthorizationStatus,
StartTransactionRequest,
StartTransactionResponse,
StopTransactionReason,
import {
BroadcastChannelProcedureName,
BroadcastChannelRequest,
+ BroadcastChannelRequestPayload,
+ BroadcastChannelResponsePayload,
+ MessageEvent,
} from '../types/WorkerBroadcastChannel';
+import { ResponseStatus } from '../ui/web/src/type/UIProtocol';
+import logger from '../utils/Logger';
import ChargingStation from './ChargingStation';
import WorkerBroadcastChannel from './WorkerBroadcastChannel';
const moduleName = 'ChargingStationWorkerBroadcastChannel';
-type MessageEvent = { data: unknown };
+type CommandResponse = StartTransactionResponse | StopTransactionResponse;
export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
private readonly chargingStation: ChargingStation;
super();
this.chargingStation = chargingStation;
this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
+ this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
}
private async requestHandler(messageEvent: MessageEvent): Promise<void> {
- const [, command, payload] = messageEvent.data as BroadcastChannelRequest;
+ if (this.isResponse(messageEvent.data)) {
+ return;
+ }
- if (payload.hashId !== this.chargingStation.hashId) {
+ const [uuid, command, requestPayload] = messageEvent.data as BroadcastChannelRequest;
+
+ if (requestPayload?.hashId !== this.chargingStation.hashId) {
return;
}
- // TODO: return a response stating the command success or failure
+ let responsePayload: BroadcastChannelResponsePayload;
+ let commandResponse: CommandResponse;
+ try {
+ commandResponse = await this.commandHandler(command, requestPayload);
+ if (commandResponse === undefined) {
+ responsePayload = { status: ResponseStatus.SUCCESS };
+ } else {
+ responsePayload = { status: this.commandResponseToResponseStatus(commandResponse) };
+ }
+ } catch (error) {
+ logger.error(
+ `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
+ error
+ );
+ responsePayload = {
+ status: ResponseStatus.FAILURE,
+ command,
+ requestPayload,
+ commandResponse,
+ errorMessage: (error as Error).message,
+ errorStack: (error as Error).stack,
+ };
+ }
+ this.sendResponse([uuid, responsePayload]);
+ }
+
+ private messageErrorHandler(messageEvent: MessageEvent): void {
+ logger.error(
+ `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
+ { messageEvent, messageEventData: messageEvent.data }
+ );
+ }
+
+ private async commandHandler(
+ command: BroadcastChannelProcedureName,
+ requestPayload: BroadcastChannelRequestPayload
+ ): Promise<CommandResponse | undefined> {
switch (command) {
case BroadcastChannelProcedureName.START_TRANSACTION:
- await this.chargingStation.ocppRequestService.requestHandler<
+ return this.chargingStation.ocppRequestService.requestHandler<
StartTransactionRequest,
StartTransactionResponse
>(this.chargingStation, RequestCommand.START_TRANSACTION, {
- connectorId: payload.connectorId,
- idTag: payload.idTag,
+ connectorId: requestPayload.connectorId,
+ idTag: requestPayload.idTag,
});
- break;
case BroadcastChannelProcedureName.STOP_TRANSACTION:
- await this.chargingStation.ocppRequestService.requestHandler<
+ return this.chargingStation.ocppRequestService.requestHandler<
StopTransactionRequest,
StopTransactionResponse
>(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
- transactionId: payload.transactionId,
+ transactionId: requestPayload.transactionId,
meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
- payload.transactionId
+ requestPayload.transactionId
),
- idTag: this.chargingStation.getTransactionIdTag(payload.transactionId),
+ idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
reason: StopTransactionReason.NONE,
});
- break;
case BroadcastChannelProcedureName.START_CHARGING_STATION:
this.chargingStation.start();
break;
case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
await this.chargingStation.stop();
break;
+ default:
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ throw new BaseError(`Unknown broadcast channel command: ${command}`);
+ }
+ }
+
+ private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
+ if (commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
+ return ResponseStatus.SUCCESS;
}
+ return ResponseStatus.FAILURE;
}
}
--- /dev/null
+import { BroadcastChannelResponse, MessageEvent } from '../types/WorkerBroadcastChannel';
+import logger from '../utils/Logger';
+import AbstractUIService from './ui-server/ui-services/AbstractUIService';
+import WorkerBroadcastChannel from './WorkerBroadcastChannel';
+
+const moduleName = 'UIServiceWorkerBroadcastChannel';
+
+export default class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel {
+ private uiService: AbstractUIService;
+
+ constructor(uiService: AbstractUIService) {
+ super();
+ this.uiService = uiService;
+ this.onmessage = this.responseHandler.bind(this) as (message: MessageEvent) => void;
+ this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
+ }
+
+ private responseHandler(messageEvent: MessageEvent): void {
+ if (this.isRequest(messageEvent.data)) {
+ return;
+ }
+ const [uuid, responsePayload] = messageEvent.data as BroadcastChannelResponse;
+
+ this.uiService.sendResponse(uuid, responsePayload);
+ }
+
+ private messageErrorHandler(messageEvent: MessageEvent): void {
+ logger.error(
+ `${this.uiService.logPrefix(moduleName, 'messageErrorHandler')} Error at handling message:`,
+ { messageEvent, messageEventData: messageEvent.data }
+ );
+ }
+}
import { BroadcastChannelRequest, BroadcastChannelResponse } from '../types/WorkerBroadcastChannel';
-export default class WorkerBroadcastChannel extends BroadcastChannel {
- constructor() {
+export default abstract class WorkerBroadcastChannel extends BroadcastChannel {
+ protected constructor() {
super('worker');
}
this.postMessage(request);
}
- public sendResponse(response: BroadcastChannelResponse): void {
+ protected sendResponse(response: BroadcastChannelResponse): void {
this.postMessage(response);
}
+
+ protected isRequest(message: any): boolean {
+ return Array.isArray(message) && message.length === 3;
+ }
+
+ protected isResponse(message: any): boolean {
+ return Array.isArray(message) && message.length === 2;
+ }
}
);
} catch (error) {
// Log
- logger.error(chargingStation.logPrefix() + ' Handle request error:', error);
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
+ error
+ );
throw error;
}
} else {
}
throw new OCPPError(
ErrorType.NOT_SUPPORTED,
- `${moduleName}.requestHandler: Unsupported OCPP command '${commandName}'`,
+ `Unsupported OCPP command '${commandName}'`,
commandName,
commandParams
);
throw new OCPPError(
ErrorType.NOT_SUPPORTED,
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
- `${moduleName}.buildRequestPayload: Unsupported OCPP command '${commandName}'`,
+ `Unsupported OCPP command '${commandName}'`,
commandName,
commandParams
);
this.validatePayload(chargingStation, commandName, payload);
await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
} catch (error) {
- logger.error(chargingStation.logPrefix() + ' Handle request response error:', error);
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
+ error
+ );
throw error;
}
} else {
// Throw exception
throw new OCPPError(
ErrorType.NOT_IMPLEMENTED,
- `${commandName} is not implemented to handle request response PDU ${JSON.stringify(
+ `${commandName} is not implemented to handle response PDU ${JSON.stringify(
payload,
null,
2
} else {
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `${commandName} cannot be issued to handle request response PDU ${JSON.stringify(
+ `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
payload,
null,
2
this.uiServices.set(version, UIServiceFactory.getUIServiceImplementation(version, this));
}
// FIXME: check connection validity
- socket.on('message', (messageData) => {
+ socket.on('message', (rawData) => {
this.uiServices
.get(version)
- .requestHandler(messageData)
- .catch((error) => {
- logger.error(
- `${this.logPrefix(
- moduleName,
- 'start.socket.onmessage'
- )} Error while handling message:`,
- error
- );
+ .requestHandler(rawData)
+ .catch(() => {
+ /* Error caught by AbstractUIService */
});
});
socket.on('error', (error) => {
import logger from '../../../utils/Logger';
import Utils from '../../../utils/Utils';
import Bootstrap from '../../Bootstrap';
-import WorkerBroadcastChannel from '../../WorkerBroadcastChannel';
+import UIServiceWorkerBroadcastChannel from '../../UIServiceWorkerBroadcastChannel';
import { AbstractUIServer } from '../AbstractUIServer';
const moduleName = 'AbstractUIService';
protected readonly version: ProtocolVersion;
protected readonly uiServer: AbstractUIServer;
protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>;
- protected workerBroadcastChannel: WorkerBroadcastChannel;
+ protected workerBroadcastChannel: UIServiceWorkerBroadcastChannel;
constructor(uiServer: AbstractUIServer, version: ProtocolVersion) {
this.version = version;
[ProcedureName.START_SIMULATOR, this.handleStartSimulator.bind(this)],
[ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)],
]);
- this.workerBroadcastChannel = new WorkerBroadcastChannel();
+ this.workerBroadcastChannel = new UIServiceWorkerBroadcastChannel(this);
}
public async requestHandler(request: RawData): Promise<void> {
let messageId: string;
let command: ProcedureName;
- let requestPayload: RequestPayload;
+ let requestPayload: RequestPayload | undefined;
let responsePayload: ResponsePayload;
try {
[messageId, command, requestPayload] = this.requestValidation(request);
);
}
- // Call the message handler to build the response payload
+ // Call the request handler to build the response payload
responsePayload = await this.requestHandlers.get(command)(messageId, requestPayload);
} catch (error) {
// Log
logger.error(
- `${this.uiServer.logPrefix(moduleName, 'messageHandler')} Handle message error:`,
+ `${this.uiServer.logPrefix(moduleName, 'messageHandler')} Handle request error:`,
error
);
- // Send the message response failure
- this.uiServer.sendResponse(
- this.buildProtocolResponse(messageId ?? 'error', {
- status: ResponseStatus.FAILURE,
- command,
- requestPayload,
- errorMessage: (error as Error).message,
- errorStack: (error as Error).stack,
- })
- );
- throw error;
+ responsePayload = {
+ status: ResponseStatus.FAILURE,
+ command,
+ requestPayload,
+ responsePayload,
+ errorMessage: (error as Error).message,
+ errorStack: (error as Error).stack,
+ };
+ }
+
+ if (responsePayload !== undefined) {
+ // Send the response
+ this.uiServer.sendResponse(this.buildProtocolResponse(messageId ?? 'error', responsePayload));
}
+ }
+
+ public sendRequest(
+ messageId: string,
+ procedureName: ProcedureName,
+ requestPayload: RequestPayload
+ ): void {
+ this.uiServer.sendRequest(this.buildProtocolRequest(messageId, procedureName, requestPayload));
+ }
- // Send the message response success
+ public sendResponse(messageId: string, responsePayload: ResponsePayload): void {
this.uiServer.sendResponse(this.buildProtocolResponse(messageId, responsePayload));
}
- protected buildProtocolRequest(
+ public logPrefix(modName: string, methodName: string): string {
+ return `${this.uiServer.logPrefix(modName, methodName)}`;
+ }
+
+ private buildProtocolRequest(
messageId: string,
procedureName: ProcedureName,
- payload: RequestPayload
+ requestPayload: RequestPayload
): string {
- return JSON.stringify([messageId, procedureName, payload] as ProtocolRequest);
+ return JSON.stringify([messageId, procedureName, requestPayload] as ProtocolRequest);
}
- protected buildProtocolResponse(messageId: string, payload: ResponsePayload): string {
- return JSON.stringify([messageId, payload] as ProtocolResponse);
+ private buildProtocolResponse(messageId: string, responsePayload: ResponsePayload): string {
+ return JSON.stringify([messageId, responsePayload] as ProtocolResponse);
}
// Validate the raw data received from the WebSocket
ProtocolRequestHandler,
ProtocolVersion,
RequestPayload,
- ResponsePayload,
- ResponseStatus,
} from '../../../types/UIProtocol';
import {
BroadcastChannelProcedureName,
);
}
- private handleStartTransaction(uuid: string, payload: RequestPayload): ResponsePayload {
+ private handleStartTransaction(uuid: string, payload: RequestPayload): void {
this.workerBroadcastChannel.sendRequest([
uuid,
BroadcastChannelProcedureName.START_TRANSACTION,
payload as BroadcastChannelRequestPayload,
]);
- return { status: ResponseStatus.SUCCESS };
}
- private handleStopTransaction(uuid: string, payload: RequestPayload): ResponsePayload {
+ private handleStopTransaction(uuid: string, payload: RequestPayload): void {
this.workerBroadcastChannel.sendRequest([
uuid,
BroadcastChannelProcedureName.STOP_TRANSACTION,
payload as BroadcastChannelRequestPayload,
]);
- return { status: ResponseStatus.SUCCESS };
}
- private handleStartChargingStation(uuid: string, payload: RequestPayload): ResponsePayload {
+ private handleStartChargingStation(uuid: string, payload: RequestPayload): void {
this.workerBroadcastChannel.sendRequest([
uuid,
BroadcastChannelProcedureName.START_CHARGING_STATION,
payload as BroadcastChannelRequestPayload,
]);
- return { status: ResponseStatus.SUCCESS };
}
- private handleStopChargingStation(uuid: string, payload: RequestPayload): ResponsePayload {
+ private handleStopChargingStation(uuid: string, payload: RequestPayload): void {
this.workerBroadcastChannel.sendRequest([
uuid,
BroadcastChannelProcedureName.STOP_CHARGING_STATION,
payload as BroadcastChannelRequestPayload,
]);
- return { status: ResponseStatus.SUCCESS };
}
}
export type ProtocolRequestHandler = (
uuid?: string,
payload?: RequestPayload
-) => ResponsePayload | Promise<ResponsePayload>;
+) => undefined | Promise<undefined> | ResponsePayload | Promise<ResponsePayload>;
export enum ProcedureName {
LIST_CHARGING_STATIONS = 'listChargingStations',
STOP_TRANSACTION = 'stopTransaction',
}
-interface BroadcastChannelBasePayload extends JsonObject {
+export interface BroadcastChannelRequestPayload extends Omit<RequestPayload, 'hashId'> {
hashId: string;
-}
-
-export interface BroadcastChannelRequestPayload
- extends BroadcastChannelBasePayload,
- Omit<RequestPayload, 'hashId'> {
connectorId?: number;
transactionId?: number;
idTag?: string;
}
export type BroadcastChannelResponsePayload = ResponsePayload;
+
+export type MessageEvent = { data: BroadcastChannelRequest | BroadcastChannelResponse };