Improve and fix error handling at sending OCPP commands
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 16 Jul 2021 10:05:29 +0000 (12:05 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 16 Jul 2021 10:05:29 +0000 (12:05 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
package-lock.json
package.json
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCCP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/worker/WorkerSet.ts

index d4275beb682d7cb4fc80d3b3a4b7a5b3e2e0f5db..3044e93f7c1087e28c05064c4d79dcc70c242071 100644 (file)
       "dev": true
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.2.tgz",
-      "integrity": "sha512-PGqpLLzHSxq956rzNGasO3GsAPf2lY9lDUBXhS++SKonglUmJypaUtcKzRtUte8CV7nruwnDxtLUKpVxs0wQBw==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.3.tgz",
+      "integrity": "sha512-jW8sEFu1ZeaV8xzwsfi6Vgtty2jf7/lJmQmDkDruBjYAbx5DA8JtbcMnP0rNPUG+oH5GoQBTSp+9613BzuIpYg==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "4.28.2",
-        "@typescript-eslint/scope-manager": "4.28.2",
+        "@typescript-eslint/experimental-utils": "4.28.3",
+        "@typescript-eslint/scope-manager": "4.28.3",
         "debug": "^4.3.1",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.1.0",
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.2.tgz",
-      "integrity": "sha512-MwHPsL6qo98RC55IoWWP8/opTykjTp4JzfPu1VfO2Z0MshNP0UZ1GEV5rYSSnZSUI8VD7iHvtIPVGW5Nfh7klQ==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.3.tgz",
+      "integrity": "sha512-zZYl9TnrxwEPi3FbyeX0ZnE8Hp7j3OCR+ELoUfbwGHGxWnHg9+OqSmkw2MoCVpZksPCZYpQzC559Ee9pJNHTQw==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.7",
-        "@typescript-eslint/scope-manager": "4.28.2",
-        "@typescript-eslint/types": "4.28.2",
-        "@typescript-eslint/typescript-estree": "4.28.2",
+        "@typescript-eslint/scope-manager": "4.28.3",
+        "@typescript-eslint/types": "4.28.3",
+        "@typescript-eslint/typescript-estree": "4.28.3",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       },
       }
     },
     "@typescript-eslint/parser": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.2.tgz",
-      "integrity": "sha512-Q0gSCN51eikAgFGY+gnd5p9bhhCUAl0ERMiDKrTzpSoMYRubdB8MJrTTR/BBii8z+iFwz8oihxd0RAdP4l8w8w==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.3.tgz",
+      "integrity": "sha512-ZyWEn34bJexn/JNYvLQab0Mo5e+qqQNhknxmc8azgNd4XqspVYR5oHq9O11fLwdZMRcj4by15ghSlIEq+H5ltQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "4.28.2",
-        "@typescript-eslint/types": "4.28.2",
-        "@typescript-eslint/typescript-estree": "4.28.2",
+        "@typescript-eslint/scope-manager": "4.28.3",
+        "@typescript-eslint/types": "4.28.3",
+        "@typescript-eslint/typescript-estree": "4.28.3",
         "debug": "^4.3.1"
       },
       "dependencies": {
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.2.tgz",
-      "integrity": "sha512-MqbypNjIkJFEFuOwPWNDjq0nqXAKZvDNNs9yNseoGBB1wYfz1G0WHC2AVOy4XD7di3KCcW3+nhZyN6zruqmp2A==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.3.tgz",
+      "integrity": "sha512-/8lMisZ5NGIzGtJB+QizQ5eX4Xd8uxedFfMBXOKuJGP0oaBBVEMbJVddQKDXyyB0bPlmt8i6bHV89KbwOelJiQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.28.2",
-        "@typescript-eslint/visitor-keys": "4.28.2"
+        "@typescript-eslint/types": "4.28.3",
+        "@typescript-eslint/visitor-keys": "4.28.3"
       }
     },
     "@typescript-eslint/types": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.2.tgz",
-      "integrity": "sha512-Gr15fuQVd93uD9zzxbApz3wf7ua3yk4ZujABZlZhaxxKY8ojo448u7XTm/+ETpy0V0dlMtj6t4VdDvdc0JmUhA==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.3.tgz",
+      "integrity": "sha512-kQFaEsQBQVtA9VGVyciyTbIg7S3WoKHNuOp/UF5RG40900KtGqfoiETWD/v0lzRXc+euVE9NXmfer9dLkUJrkA==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.2.tgz",
-      "integrity": "sha512-86lLstLvK6QjNZjMoYUBMMsULFw0hPHJlk1fzhAVoNjDBuPVxiwvGuPQq3fsBMCxuDJwmX87tM/AXoadhHRljg==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.3.tgz",
+      "integrity": "sha512-YAb1JED41kJsqCQt1NcnX5ZdTA93vKFCMP4lQYG6CFxd0VzDJcKttRlMrlG+1qiWAw8+zowmHU1H0OzjWJzR2w==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.28.2",
-        "@typescript-eslint/visitor-keys": "4.28.2",
+        "@typescript-eslint/types": "4.28.3",
+        "@typescript-eslint/visitor-keys": "4.28.3",
         "debug": "^4.3.1",
         "globby": "^11.0.3",
         "is-glob": "^4.0.1",
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "4.28.2",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz",
-      "integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==",
+      "version": "4.28.3",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.3.tgz",
+      "integrity": "sha512-ri1OzcLnk1HH4gORmr1dllxDzzrN6goUIz/P4MHFV0YZJDCADPR3RvYNp0PW2SetKTThar6wlbFTL00hV2Q+fg==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.28.2",
+        "@typescript-eslint/types": "4.28.3",
         "eslint-visitor-keys": "^2.0.0"
       }
     },
       }
     },
     "rollup": {
-      "version": "2.53.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.1.tgz",
-      "integrity": "sha512-yiTCvcYXZEulNWNlEONOQVlhXA/hgxjelFSjNcrwAAIfYx/xqjSHwqg/cCaWOyFRKr+IQBaXwt723m8tCaIUiw==",
+      "version": "2.53.2",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz",
+      "integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==",
       "dev": true,
       "requires": {
         "fsevents": "~2.3.2"
index 9faf8ff99ad381aac42d8f08c0dec077c8463bfa..e9281bb4e7a9a8d8dde41acd06d03ff10af5a055 100644 (file)
@@ -81,8 +81,8 @@
     "@types/tar": "^4.0.5",
     "@types/uuid": "^8.3.1",
     "@types/ws": "^7.4.6",
-    "@typescript-eslint/eslint-plugin": "^4.28.2",
-    "@typescript-eslint/parser": "^4.28.2",
+    "@typescript-eslint/eslint-plugin": "^4.28.3",
+    "@typescript-eslint/parser": "^4.28.3",
     "auto-changelog": "^2.3.0",
     "clinic": "^9.0.0",
     "cross-env": "^7.0.3",
@@ -98,7 +98,7 @@
     "nyc": "^15.1.0",
     "release-it": "^14.10.0",
     "robohydra": "^0.6.9",
-    "rollup": "^2.53.1",
+    "rollup": "^2.53.2",
     "rollup-plugin-analyzer": "^4.0.0",
     "rollup-plugin-copy": "^3.4.0",
     "rollup-plugin-delete": "^2.0.0",
index c1b51ce2273cb57d957aaacfb5a632e66e77dabd..bd25116b990170ab1a4c776a693306a359d23021 100644 (file)
@@ -140,7 +140,7 @@ export default class ChargingStation {
         break;
       default:
         logger.error(errMsg);
-        throw Error(errMsg);
+        throw new Error(errMsg);
     }
     return !Utils.isUndefined(this.stationInfo.voltageOut) ? this.stationInfo.voltageOut : defaultVoltageOut;
   }
@@ -228,7 +228,7 @@ export default class ChargingStation {
     }
     const sampledValueTemplates: SampledValueTemplate[] = this.getConnector(connectorId).MeterValues;
     for (let index = 0; !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length; index++) {
-      if (!Constants.SUPPORTED_MEASURANDS.includes(sampledValueTemplates[index]?.measurand)) {
+      if (!Constants.SUPPORTED_MEASURANDS.includes(sampledValueTemplates[index]?.measurand ?? MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER)) {
         logger.warn(`${this.logPrefix()} Unsupported MeterValues measurand ${measurand} ${phase ? `on phase ${phase} ` : ''}in template on connectorId ${connectorId}`);
         continue;
       } else if (phase && sampledValueTemplates[index]?.phase === phase && sampledValueTemplates[index]?.measurand === measurand
index 4a0e82d602c1d110cc82f951ea84df21176abbf6..58814495ba70b226b6151344d58d0373aac2fc34 100644 (file)
@@ -4,12 +4,12 @@ import { ChangeAvailabilityRequest, ChangeConfigurationRequest, ClearChargingPro
 import { ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, GetConfigurationResponse, GetDiagnosticsResponse, SetChargingProfileResponse, UnlockConnectorResponse } from '../../../types/ocpp/1.6/Responses';
 import { ChargingProfilePurposeType, OCPP16ChargingProfile } from '../../../types/ocpp/1.6/ChargingProfile';
 import { Client, FTPResponse } from 'basic-ftp';
+import { IncomingRequestCommand, RequestCommand } from '../../../types/ocpp/Requests';
 import { OCPP16AuthorizationStatus, OCPP16StopTransactionReason } from '../../../types/ocpp/1.6/Transaction';
 
 import Constants from '../../../utils/Constants';
 import { DefaultResponse } from '../../../types/ocpp/Responses';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
-import { IncomingRequestCommand } from '../../../types/ocpp/Requests';
 import { MessageType } from '../../../types/ocpp/MessageType';
 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
 import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
@@ -132,10 +132,10 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
   private handleRequestChangeConfiguration(commandPayload: ChangeConfigurationRequest): ChangeConfigurationResponse {
     // JSON request fields type sanity check
     if (!Utils.isString(commandPayload.key)) {
-      logger.error(`${this.chargingStation.logPrefix()} ChangeConfiguration request key field is not a string:`, commandPayload);
+      logger.error(`${this.chargingStation.logPrefix()} ${RequestCommand.CHANGE_CONFIGURATION} request key field is not a string:`, commandPayload);
     }
     if (!Utils.isString(commandPayload.value)) {
-      logger.error(`${this.chargingStation.logPrefix()} ChangeConfiguration request value field is not a string:`, commandPayload);
+      logger.error(`${this.chargingStation.logPrefix()} ${RequestCommand.CHANGE_CONFIGURATION} request value field is not a string:`, commandPayload);
     }
     const keyToChange = this.chargingStation.getConfigurationKey(commandPayload.key, true);
     if (!keyToChange) {
@@ -380,9 +380,9 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
             }
             return { fileName: diagnosticsArchive };
           }
-          throw Error(`Diagnostics transfer failed with error code ${accessResponse.code.toString()}${uploadResponse?.code && '|' + uploadResponse?.code.toString()}`);
+          throw new Error(`Diagnostics transfer failed with error code ${accessResponse.code.toString()}${uploadResponse?.code && '|' + uploadResponse?.code.toString()}`);
         }
-        throw Error(`Diagnostics transfer failed with error code ${accessResponse.code.toString()}${uploadResponse?.code && '|' + uploadResponse?.code.toString()}`);
+        throw new Error(`Diagnostics transfer failed with error code ${accessResponse.code.toString()}${uploadResponse?.code && '|' + uploadResponse?.code.toString()}`);
       } catch (error) {
         await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(OCPP16DiagnosticsStatus.UploadFailed);
         this.handleIncomingRequestError(IncomingRequestCommand.GET_DIAGNOSTICS, error);
index 7302f94cff300fd7b7f159dbb0587d56c9c3b6c1..b19e3192af7c0af71ee8e46773310ff804dcb99a 100644 (file)
@@ -216,7 +216,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
             break;
           default:
             logger.error(errMsg);
-            throw Error(errMsg);
+            throw new Error(errMsg);
         }
         meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(powerSampledValueTemplate, powerMeasurandValues.allPhases));
         const sampledValuesIndex = meterValue.sampledValue.length - 1;
@@ -282,7 +282,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
             break;
           default:
             logger.error(errMsg);
-            throw Error(errMsg);
+            throw new Error(errMsg);
         }
         meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(currentSampledValueTemplate, currentMeasurandValues.allPhases));
         const sampledValuesIndex = meterValue.sampledValue.length - 1;
@@ -338,32 +338,48 @@ export default class OCPP16RequestService extends OCPPRequestService {
   }
 
   public async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, beginMeterValue: OCPP16MeterValue): Promise<void> {
-    const payload: MeterValuesRequest = {
-      connectorId,
-      transactionId,
-      meterValue: beginMeterValue,
-    };
-    await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
+    try {
+      const payload: MeterValuesRequest = {
+        connectorId,
+        transactionId,
+        meterValue: beginMeterValue,
+      };
+      await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
+    } catch (error) {
+      this.handleRequestError(OCPP16RequestCommand.METER_VALUES, error);
+    }
   }
 
   public async sendTransactionEndMeterValues(connectorId: number, transactionId: number, endMeterValue: OCPP16MeterValue): Promise<void> {
-    const payload: MeterValuesRequest = {
-      connectorId,
-      transactionId,
-      meterValue: endMeterValue,
-    };
-    await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
+    try {
+      const payload: MeterValuesRequest = {
+        connectorId,
+        transactionId,
+        meterValue: endMeterValue,
+      };
+      await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
+    } catch (error) {
+      this.handleRequestError(OCPP16RequestCommand.METER_VALUES, error);
+    }
   }
 
   public async sendDiagnosticsStatusNotification(diagnosticsStatus: OCPP16DiagnosticsStatus): Promise<void> {
-    const payload: DiagnosticsStatusNotificationRequest = {
-      status: diagnosticsStatus
-    };
-    await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION);
+    try {
+      const payload: DiagnosticsStatusNotificationRequest = {
+        status: diagnosticsStatus
+      };
+      await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION);
+    } catch (error) {
+      this.handleRequestError(OCPP16RequestCommand.METER_VALUES, error);
+    }
   }
 
   public async sendError(messageId: string, error: OCPPError, commandName: OCPP16RequestCommand | OCPP16IncomingRequestCommand): Promise<unknown> {
-    // Send error
-    return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName);
+    try {
+      // Send error
+      return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName);
+    } catch (err) {
+      this.handleRequestError(commandName as OCPP16RequestCommand, err);
+    }
   }
 }
index 0bfef77e4a09b616efede9fd1054ef404cf00a87..d12acffc9c42938d4dac5967db92e22054e7181a 100644 (file)
@@ -54,6 +54,10 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       logger.debug(this.chargingStation.logPrefix() + ' Trying to start a transaction on an already used connector ' + connectorId.toString() + ': %j', this.chargingStation.getConnector(connectorId));
       return;
     }
+    if (this.chargingStation.getConnector(connectorId)?.status !== OCPP16ChargePointStatus.AVAILABLE) {
+      logger.error(`${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${this.chargingStation.getConnector(connectorId)?.status}`);
+      return;
+    }
 
     if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
       this.chargingStation.getConnector(connectorId).transactionStarted = true;
index 34a65de8f1ab0c379d0ee95af02f78f5d52afd1d..ead2558aa7cd6d07f94e4e37e81b475f1622bebc 100644 (file)
@@ -10,19 +10,19 @@ export class OCPP16ServiceUtils {
     if (Utils.isUndefined(chargingStation.stationInfo.powerDivider)) {
       const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
       logger.error(errMsg);
-      throw Error(errMsg);
+      throw new Error(errMsg);
     } else if (chargingStation.stationInfo?.powerDivider <= 0) {
       const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
       logger.error(errMsg);
-      throw Error(errMsg);
+      throw new Error(errMsg);
     }
   }
 
   public static buildSampledValue(sampledValueTemplate: SampledValueTemplate, value: number, context?: MeterValueContext, phase?: OCPP16MeterValuePhase): OCPP16SampledValue {
-    const sampledValueValue = value ?? (sampledValueTemplate.value ?? null);
-    const sampledValueContext = context ?? (sampledValueTemplate.context ?? null);
-    const sampledValueLocation = sampledValueTemplate.location ?? OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate.measurand ?? null);
-    const sampledValuePhase = phase ?? (sampledValueTemplate.phase ?? null);
+    const sampledValueValue = value ?? (sampledValueTemplate?.value ?? null);
+    const sampledValueContext = context ?? (sampledValueTemplate?.context ?? null);
+    const sampledValueLocation = sampledValueTemplate?.location ?? OCPP16ServiceUtils.getMeasurandDefaultLocation(sampledValueTemplate?.measurand ?? null);
+    const sampledValuePhase = phase ?? (sampledValueTemplate?.phase ?? null);
     return {
       ...!Utils.isNullOrUndefined(sampledValueTemplate.unit) && { unit: sampledValueTemplate.unit },
       ...!Utils.isNullOrUndefined(sampledValueContext) && { context: sampledValueContext },
index 21f9f482997a6ffad898b39e2bcb6c2d11af46cf..2ccd27961941ac5c56eb3ddb1a463138dea21495 100644 (file)
@@ -33,7 +33,7 @@ export default class WorkerSet<T> extends WorkerAbstract {
    */
   public async addElement(elementData: T): Promise<void> {
     if (!this.workerSet) {
-      throw Error('Cannot add a WorkerSet element: workers\' set does not exist');
+      throw new Error('Cannot add a WorkerSet element: workers\' set does not exist');
     }
     if (this.getLastWorkerSetElement().numberOfWorkerElements >= this.maxElementsPerWorker) {
       this.startWorker();