| 1 | import BaseError from '../exception/BaseError'; |
| 2 | import type OCPPError from '../exception/OCPPError'; |
| 3 | import { RequestCommand, type StatusNotificationRequest } from '../types/ocpp/Requests'; |
| 4 | import type { StatusNotificationResponse } from '../types/ocpp/Responses'; |
| 5 | import { |
| 6 | AuthorizationStatus, |
| 7 | StartTransactionRequest, |
| 8 | StartTransactionResponse, |
| 9 | StopTransactionRequest, |
| 10 | StopTransactionResponse, |
| 11 | } from '../types/ocpp/Transaction'; |
| 12 | import { |
| 13 | BroadcastChannelProcedureName, |
| 14 | BroadcastChannelRequest, |
| 15 | BroadcastChannelRequestPayload, |
| 16 | BroadcastChannelResponsePayload, |
| 17 | MessageEvent, |
| 18 | } from '../types/WorkerBroadcastChannel'; |
| 19 | import { ResponseStatus } from '../ui/web/src/types/UIProtocol'; |
| 20 | import logger from '../utils/Logger'; |
| 21 | import Utils from '../utils/Utils'; |
| 22 | import type ChargingStation from './ChargingStation'; |
| 23 | import WorkerBroadcastChannel from './WorkerBroadcastChannel'; |
| 24 | |
| 25 | const moduleName = 'ChargingStationWorkerBroadcastChannel'; |
| 26 | |
| 27 | type CommandResponse = |
| 28 | | StartTransactionResponse |
| 29 | | StopTransactionResponse |
| 30 | | StatusNotificationResponse; |
| 31 | |
| 32 | export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel { |
| 33 | private readonly chargingStation: ChargingStation; |
| 34 | |
| 35 | constructor(chargingStation: ChargingStation) { |
| 36 | super(); |
| 37 | this.chargingStation = chargingStation; |
| 38 | this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void; |
| 39 | this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void; |
| 40 | } |
| 41 | |
| 42 | private async requestHandler(messageEvent: MessageEvent): Promise<void> { |
| 43 | if (this.isResponse(messageEvent.data) === true) { |
| 44 | return; |
| 45 | } |
| 46 | const [uuid, command, requestPayload] = this.validateMessageEvent(messageEvent) |
| 47 | .data as BroadcastChannelRequest; |
| 48 | |
| 49 | if (requestPayload?.hashIds !== undefined || requestPayload?.hashId !== undefined) { |
| 50 | if ( |
| 51 | requestPayload?.hashId === undefined && |
| 52 | requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false |
| 53 | ) { |
| 54 | return; |
| 55 | } |
| 56 | if ( |
| 57 | requestPayload?.hashIds === undefined && |
| 58 | requestPayload?.hashId !== this.chargingStation.stationInfo.hashId |
| 59 | ) { |
| 60 | return; |
| 61 | } |
| 62 | if (requestPayload?.hashId !== undefined) { |
| 63 | logger.warn( |
| 64 | `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead` |
| 65 | ); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | let responsePayload: BroadcastChannelResponsePayload; |
| 70 | let commandResponse: CommandResponse; |
| 71 | try { |
| 72 | commandResponse = await this.commandHandler(command, requestPayload); |
| 73 | if (commandResponse === undefined) { |
| 74 | responsePayload = { |
| 75 | hashId: this.chargingStation.stationInfo.hashId, |
| 76 | status: ResponseStatus.SUCCESS, |
| 77 | }; |
| 78 | } else { |
| 79 | responsePayload = { |
| 80 | hashId: this.chargingStation.stationInfo.hashId, |
| 81 | status: this.commandResponseToResponseStatus(commandResponse), |
| 82 | }; |
| 83 | } |
| 84 | } catch (error) { |
| 85 | logger.error( |
| 86 | `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`, |
| 87 | error |
| 88 | ); |
| 89 | responsePayload = { |
| 90 | hashId: this.chargingStation.stationInfo.hashId, |
| 91 | status: ResponseStatus.FAILURE, |
| 92 | command, |
| 93 | requestPayload, |
| 94 | commandResponse, |
| 95 | errorMessage: (error as Error).message, |
| 96 | errorStack: (error as Error).stack, |
| 97 | errorDetails: (error as OCPPError).details, |
| 98 | }; |
| 99 | } |
| 100 | this.sendResponse([uuid, responsePayload]); |
| 101 | } |
| 102 | |
| 103 | private messageErrorHandler(messageEvent: MessageEvent): void { |
| 104 | logger.error( |
| 105 | `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`, |
| 106 | { messageEvent } |
| 107 | ); |
| 108 | } |
| 109 | |
| 110 | private async commandHandler( |
| 111 | command: BroadcastChannelProcedureName, |
| 112 | requestPayload: BroadcastChannelRequestPayload |
| 113 | ): Promise<CommandResponse | undefined> { |
| 114 | switch (command) { |
| 115 | case BroadcastChannelProcedureName.START_CHARGING_STATION: |
| 116 | this.chargingStation.start(); |
| 117 | break; |
| 118 | case BroadcastChannelProcedureName.STOP_CHARGING_STATION: |
| 119 | await this.chargingStation.stop(); |
| 120 | break; |
| 121 | case BroadcastChannelProcedureName.OPEN_CONNECTION: |
| 122 | this.chargingStation.openWSConnection(); |
| 123 | break; |
| 124 | case BroadcastChannelProcedureName.CLOSE_CONNECTION: |
| 125 | this.chargingStation.closeWSConnection(); |
| 126 | break; |
| 127 | case BroadcastChannelProcedureName.START_TRANSACTION: |
| 128 | return this.chargingStation.ocppRequestService.requestHandler< |
| 129 | StartTransactionRequest, |
| 130 | StartTransactionResponse |
| 131 | >(this.chargingStation, RequestCommand.START_TRANSACTION, { |
| 132 | connectorId: requestPayload.connectorId, |
| 133 | idTag: requestPayload.idTag, |
| 134 | }); |
| 135 | case BroadcastChannelProcedureName.STOP_TRANSACTION: |
| 136 | return this.chargingStation.ocppRequestService.requestHandler< |
| 137 | StopTransactionRequest, |
| 138 | StopTransactionResponse |
| 139 | >(this.chargingStation, RequestCommand.STOP_TRANSACTION, { |
| 140 | transactionId: requestPayload.transactionId, |
| 141 | meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId( |
| 142 | requestPayload.transactionId, |
| 143 | true |
| 144 | ), |
| 145 | idTag: requestPayload.idTag, |
| 146 | reason: requestPayload.reason, |
| 147 | }); |
| 148 | case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR: |
| 149 | this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds); |
| 150 | break; |
| 151 | case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR: |
| 152 | this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds); |
| 153 | break; |
| 154 | case BroadcastChannelProcedureName.STATUS_NOTIFICATION: |
| 155 | return this.chargingStation.ocppRequestService.requestHandler< |
| 156 | StatusNotificationRequest, |
| 157 | StatusNotificationResponse |
| 158 | >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, { |
| 159 | connectorId: requestPayload.connectorId, |
| 160 | errorCode: requestPayload.errorCode, |
| 161 | status: requestPayload.status, |
| 162 | ...(requestPayload.info && { info: requestPayload.info }), |
| 163 | ...(requestPayload.timestamp && { timestamp: requestPayload.timestamp }), |
| 164 | ...(requestPayload.vendorId && { vendorId: requestPayload.vendorId }), |
| 165 | ...(requestPayload.vendorErrorCode && { |
| 166 | vendorErrorCode: requestPayload.vendorErrorCode, |
| 167 | }), |
| 168 | }); |
| 169 | default: |
| 170 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions |
| 171 | throw new BaseError(`Unknown worker broadcast channel command: ${command}`); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus { |
| 176 | if ( |
| 177 | Utils.isEmptyObject(commandResponse) || |
| 178 | commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED |
| 179 | ) { |
| 180 | return ResponseStatus.SUCCESS; |
| 181 | } |
| 182 | return ResponseStatus.FAILURE; |
| 183 | } |
| 184 | } |