Introduce JSON type and use it for OCPP and internal message structure
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 4 Feb 2022 15:19:26 +0000 (16:19 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 4 Feb 2022 15:19:26 +0000 (16:19 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
15 files changed:
src/charging-station/Bootstrap.ts
src/types/ChargingStationWorker.ts
src/types/JsonType.ts [new file with mode: 0644]
src/types/Worker.ts
src/types/ocpp/1.6/ChargingProfile.ts
src/types/ocpp/1.6/MeterValues.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/1.6/Responses.ts
src/types/ocpp/1.6/Transaction.ts
src/types/ocpp/Configuration.ts
src/worker/WorkerAbstract.ts
src/worker/WorkerDynamicPool.ts
src/worker/WorkerFactory.ts
src/worker/WorkerSet.ts
src/worker/WorkerStaticPool.ts

index ce5286acd70de97586b09839d9fb4b45492eae0a..143bb5c4298ee229fe3c0d8d66ad69eebda37bf6 100644 (file)
@@ -3,6 +3,7 @@
 import { ChargingStationWorkerData, ChargingStationWorkerMessage, ChargingStationWorkerMessageEvents } from '../types/ChargingStationWorker';
 
 import Configuration from '../utils/Configuration';
+import Statistics from '../types/Statistics';
 import { Storage } from '../performance/storage/Storage';
 import { StorageFactory } from '../performance/storage/StorageFactory';
 import { UIServiceUtils } from './ui-websocket-services/UIServiceUtils';
@@ -17,7 +18,7 @@ import { version } from '../../package.json';
 
 export default class Bootstrap {
   private static instance: Bootstrap | null = null;
-  private workerImplementation: WorkerAbstract | null = null;
+  private workerImplementation: WorkerAbstract<ChargingStationWorkerData> | null = null;
   private readonly uiWebSocketServer!: UIWebSocketServer;
   private readonly storage!: Storage;
   private numberOfChargingStations: number;
@@ -118,11 +119,11 @@ export default class Bootstrap {
         },
         messageHandler: async (msg: ChargingStationWorkerMessage) => {
           if (msg.id === ChargingStationWorkerMessageEvents.STARTED) {
-            this.uiWebSocketServer.chargingStations.add(msg.data.id);
+            this.uiWebSocketServer.chargingStations.add(msg.data.id as string);
           } else if (msg.id === ChargingStationWorkerMessageEvents.STOPPED) {
-            this.uiWebSocketServer.chargingStations.delete(msg.data.id);
+            this.uiWebSocketServer.chargingStations.delete(msg.data.id as string);
           } else if (msg.id === ChargingStationWorkerMessageEvents.PERFORMANCE_STATISTICS) {
-            await this.storage.storePerformanceStatistics(msg.data);
+            await this.storage.storePerformanceStatistics(msg.data as unknown as Statistics);
           }
         }
       });
index a48e07688f4df67b21eee449fa407c3ba85518de..8bdc9af04047ac3df6787f8accbc4e61b2909ccb 100644 (file)
@@ -19,6 +19,6 @@ export const ChargingStationWorkerMessageEvents = {
 };
 
 
-export interface ChargingStationWorkerMessage extends Omit<WorkerMessage, 'id'> {
+export interface ChargingStationWorkerMessage extends Omit<WorkerMessage<ChargingStationWorkerData>, 'id'> {
   id: ChargingStationWorkerMessageEvents;
 }
diff --git a/src/types/JsonType.ts b/src/types/JsonType.ts
new file mode 100644 (file)
index 0000000..2bd219f
--- /dev/null
@@ -0,0 +1,5 @@
+export interface JsonType {
+  [x: string]: string | number | boolean | Date | JsonType | JsonArray;
+}
+
+type JsonArray = Array<string | number | boolean | Date | JsonType | JsonArray>;
index 8f47f57818e6e8b968bc10dce0c5bc21552df56e..7a96335535b5dbb4f98d847edbf9e37b4c7a7f26 100644 (file)
@@ -1,3 +1,4 @@
+import { JsonType } from './JsonType';
 import { PoolOptions } from 'poolifier';
 import { Worker } from 'worker_threads';
 
@@ -16,17 +17,16 @@ export interface WorkerOptions {
   messageHandler?: (message: unknown) => void | Promise<void>;
 }
 
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface WorkerData {}
+export type WorkerData = JsonType;
 
 export interface WorkerSetElement {
   worker: Worker;
   numberOfWorkerElements: number;
 }
 
-export interface WorkerMessage {
+export interface WorkerMessage<T extends WorkerData> {
   id: WorkerMessageEvents;
-  data: any;
+  data: T;
 }
 
 export enum WorkerMessageEvents {
index 5d72a7ac1b28ec184de27ed45384510abb817096..6416bf5d1bd2f0e25a10c58e79641cb4e891c874 100644 (file)
@@ -1,4 +1,6 @@
-export interface OCPP16ChargingProfile {
+import { JsonType } from '../../JsonType';
+
+export interface OCPP16ChargingProfile extends JsonType {
   chargingProfileId: number;
   transactionId?: number;
   stackLevel: number;
@@ -10,7 +12,7 @@ export interface OCPP16ChargingProfile {
   chargingSchedule: ChargingSchedule;
 }
 
-export interface ChargingSchedule {
+export interface ChargingSchedule extends JsonType {
   duration?: number;
   startSchedule?: Date;
   chargingRateUnit: ChargingRateUnitType;
@@ -18,7 +20,7 @@ export interface ChargingSchedule {
   minChargeRate?: number;
 }
 
-export interface ChargingSchedulePeriod {
+export interface ChargingSchedulePeriod extends JsonType {
   startPeriod: number;
   limit: number;
   numberPhases?: number;
index d3038e63ea9eb5c71e8cdcb4ae2f8a836823b73d..97a7d24c9fd3396a1d7d364fec63c77fa6a347c1 100644 (file)
@@ -1,4 +1,5 @@
 import { EmptyObject } from '../../EmptyObject';
+import { JsonType } from '../../JsonType';
 
 export enum MeterValueUnit {
   WATT_HOUR = 'Wh',
@@ -81,7 +82,7 @@ export enum MeterValueFormat {
   SIGNED_DATA = 'SignedData',
 }
 
-export interface OCPP16SampledValue {
+export interface OCPP16SampledValue extends JsonType {
   value?: string;
   unit?: MeterValueUnit;
   context?: MeterValueContext;
@@ -91,12 +92,12 @@ export interface OCPP16SampledValue {
   format?: MeterValueFormat;
 }
 
-export interface OCPP16MeterValue {
+export interface OCPP16MeterValue extends JsonType {
   timestamp: string;
   sampledValue: OCPP16SampledValue[];
 }
 
-export interface MeterValuesRequest {
+export interface MeterValuesRequest extends JsonType {
   connectorId: number;
   transactionId?: number;
   meterValue: OCPP16MeterValue[];
index d83417b04df20b5fa7e84f8db319151a818c073e..97b5d756e440b9478c573e48762aa9f6a7205456 100644 (file)
@@ -1,6 +1,7 @@
 import { ChargingProfilePurposeType, OCPP16ChargingProfile } from './ChargingProfile';
 
 import { EmptyObject } from '../../EmptyObject';
+import { JsonType } from '../../JsonType';
 import { OCPP16ChargePointErrorCode } from './ChargePointErrorCode';
 import { OCPP16ChargePointStatus } from './ChargePointStatus';
 import { OCPP16DiagnosticsStatus } from './DiagnosticsStatus';
@@ -35,7 +36,7 @@ export enum OCPP16IncomingRequestCommand {
 
 export type HeartbeatRequest = EmptyObject;
 
-export interface OCPP16BootNotificationRequest {
+export interface OCPP16BootNotificationRequest extends JsonType {
   chargeBoxSerialNumber?: string;
   chargePointModel: string;
   chargePointSerialNumber?: string;
@@ -47,7 +48,7 @@ export interface OCPP16BootNotificationRequest {
   meterType?: string;
 }
 
-export interface StatusNotificationRequest {
+export interface StatusNotificationRequest extends JsonType {
   connectorId: number;
   errorCode: OCPP16ChargePointErrorCode;
   info?: string;
@@ -57,26 +58,26 @@ export interface StatusNotificationRequest {
   vendorErrorCode?: string;
 }
 
-export interface ChangeConfigurationRequest {
+export interface ChangeConfigurationRequest extends JsonType {
   key: string | OCPP16StandardParametersKey;
   value: string;
 }
 
-export interface RemoteStartTransactionRequest {
+export interface RemoteStartTransactionRequest extends JsonType {
   connectorId: number;
   idTag: string;
   chargingProfile?: OCPP16ChargingProfile;
 }
 
-export interface RemoteStopTransactionRequest {
+export interface RemoteStopTransactionRequest extends JsonType {
   transactionId: number;
 }
 
-export interface UnlockConnectorRequest {
+export interface UnlockConnectorRequest extends JsonType {
   connectorId: number;
 }
 
-export interface GetConfigurationRequest {
+export interface GetConfigurationRequest extends JsonType {
   key?: string | OCPP16StandardParametersKey[];
 }
 
@@ -85,11 +86,11 @@ export enum ResetType {
   SOFT = 'Soft'
 }
 
-export interface ResetRequest {
+export interface ResetRequest extends JsonType {
   type: ResetType;
 }
 
-export interface SetChargingProfileRequest {
+export interface SetChargingProfileRequest extends JsonType {
   connectorId: number;
   csChargingProfiles: OCPP16ChargingProfile;
 }
@@ -99,19 +100,19 @@ export enum OCPP16AvailabilityType {
   OPERATIVE = 'Operative'
 }
 
-export interface ChangeAvailabilityRequest {
+export interface ChangeAvailabilityRequest extends JsonType {
   connectorId: number;
   type: OCPP16AvailabilityType;
 }
 
-export interface ClearChargingProfileRequest {
+export interface ClearChargingProfileRequest extends JsonType {
   id?: number;
   connectorId?: number;
   chargingProfilePurpose?: ChargingProfilePurposeType;
   stackLevel?: number;
 }
 
-export interface GetDiagnosticsRequest {
+export interface GetDiagnosticsRequest extends JsonType {
   location: string;
   retries?: number;
   retryInterval?: number;
@@ -119,7 +120,7 @@ export interface GetDiagnosticsRequest {
   stopTime?: Date;
 }
 
-export interface DiagnosticsStatusNotificationRequest {
+export interface DiagnosticsStatusNotificationRequest extends JsonType {
   status: OCPP16DiagnosticsStatus
 }
 
@@ -132,7 +133,7 @@ export enum MessageTrigger {
   StatusNotification = 'StatusNotification'
 }
 
-export interface OCPP16TriggerMessageRequest {
+export interface OCPP16TriggerMessageRequest extends JsonType {
   requestedMessage: MessageTrigger;
   connectorId?: number
 }
index 4f526b94dd600ad40ed8aa4d0d2e95e6c6140737..7d3487ba1f8bdc4549e2c9f298a9a111bc4c2a71 100644 (file)
@@ -1,7 +1,8 @@
 import { EmptyObject } from '../../EmptyObject';
+import { JsonType } from '../../JsonType';
 import { OCPPConfigurationKey } from '../Configuration';
 
-export interface HeartbeatResponse {
+export interface HeartbeatResponse extends JsonType {
   currentTime: string;
 }
 
@@ -11,7 +12,7 @@ export enum OCPP16UnlockStatus {
   NOT_SUPPORTED = 'NotSupported'
 }
 
-export interface UnlockConnectorResponse {
+export interface UnlockConnectorResponse extends JsonType {
   status: OCPP16UnlockStatus;
 }
 
@@ -22,7 +23,7 @@ export enum OCPP16ConfigurationStatus {
   NOT_SUPPORTED = 'NotSupported'
 }
 
-export interface ChangeConfigurationResponse {
+export interface ChangeConfigurationResponse extends JsonType {
   status: OCPP16ConfigurationStatus;
 }
 
@@ -32,7 +33,7 @@ export enum OCPP16RegistrationStatus {
   REJECTED = 'Rejected'
 }
 
-export interface OCPP16BootNotificationResponse {
+export interface OCPP16BootNotificationResponse extends JsonType {
   status: OCPP16RegistrationStatus;
   currentTime: string;
   interval: number;
@@ -40,7 +41,7 @@ export interface OCPP16BootNotificationResponse {
 
 export type StatusNotificationResponse = EmptyObject;
 
-export interface GetConfigurationResponse {
+export interface GetConfigurationResponse extends JsonType {
   configurationKey: OCPPConfigurationKey[];
   unknownKey: string[];
 }
@@ -51,7 +52,7 @@ export enum OCPP16ChargingProfileStatus {
   NOT_SUPPORTED = 'NotSupported',
 }
 
-export interface SetChargingProfileResponse {
+export interface SetChargingProfileResponse extends JsonType {
   status: OCPP16ChargingProfileStatus;
 }
 
@@ -61,7 +62,7 @@ export enum OCPP16AvailabilityStatus {
   SCHEDULED = 'Scheduled'
 }
 
-export interface ChangeAvailabilityResponse {
+export interface ChangeAvailabilityResponse extends JsonType {
   status: OCPP16AvailabilityStatus;
 }
 
@@ -70,11 +71,11 @@ export enum OCPP16ClearChargingProfileStatus {
   UNKNOWN = 'Unknown'
 }
 
-export interface ClearChargingProfileResponse {
+export interface ClearChargingProfileResponse extends JsonType {
   status: OCPP16ClearChargingProfileStatus;
 }
 
-export interface GetDiagnosticsResponse {
+export interface GetDiagnosticsResponse extends JsonType {
   fileName?: string;
 }
 
@@ -86,6 +87,6 @@ export enum OCPP16TriggerMessageStatus {
   NOT_IMPLEMENTED = 'NotImplemented'
 }
 
-export interface OCPP16TriggerMessageResponse {
+export interface OCPP16TriggerMessageResponse extends JsonType {
   status: OCPP16TriggerMessageStatus
 }
index 41bd344ec4da1b2cf27e2051636044df443bab08..bf2377778bb21e0c6096cb1f33267672f5fe8444 100644 (file)
@@ -1,3 +1,4 @@
+import { JsonType } from '../../JsonType';
 import { OCPP16MeterValue } from './MeterValues';
 
 export enum OCPP16StopTransactionReason {
@@ -23,21 +24,21 @@ export enum OCPP16AuthorizationStatus {
   CONCURRENT_TX = 'ConcurrentTx'
 }
 
-export interface IdTagInfo {
+export interface IdTagInfo extends JsonType {
   status: OCPP16AuthorizationStatus;
   parentIdTag?: string;
   expiryDate?: Date;
 }
 
-export interface AuthorizeRequest {
+export interface AuthorizeRequest extends JsonType {
   idTag: string;
 }
 
-export interface OCPP16AuthorizeResponse {
+export interface OCPP16AuthorizeResponse extends JsonType {
   idTagInfo: IdTagInfo;
 }
 
-export interface StartTransactionRequest {
+export interface StartTransactionRequest extends JsonType {
   connectorId: number;
   idTag: string;
   meterStart: number;
@@ -45,12 +46,12 @@ export interface StartTransactionRequest {
   timestamp: string;
 }
 
-export interface OCPP16StartTransactionResponse {
+export interface OCPP16StartTransactionResponse extends JsonType {
   idTagInfo: IdTagInfo;
   transactionId: number;
 }
 
-export interface StopTransactionRequest {
+export interface StopTransactionRequest extends JsonType {
   idTag?: string;
   meterStop: number;
   timestamp: string;
@@ -59,6 +60,6 @@ export interface StopTransactionRequest {
   transactionData?: OCPP16MeterValue[];
 }
 
-export interface OCPP16StopTransactionResponse {
+export interface OCPP16StopTransactionResponse extends JsonType {
   idTagInfo?: IdTagInfo;
 }
index b55c212196a53fe09f71db17c69fbd55007dca4c..b13a24ba2be17c6c04f5c65f10eef3b5c2ed4828 100644 (file)
@@ -1,5 +1,7 @@
 import { OCPP16StandardParametersKey, OCPP16SupportedFeatureProfiles, OCPP16VendorDefaultParametersKey } from './1.6/Configuration';
 
+import { JsonType } from '../JsonType';
+
 export type StandardParametersKey = OCPP16StandardParametersKey;
 
 export const StandardParametersKey = {
@@ -29,7 +31,7 @@ export enum ConnectorPhaseRotation {
   TSR = 'TSR'
 }
 
-export interface OCPPConfigurationKey {
+export interface OCPPConfigurationKey extends JsonType {
   key: string | StandardParametersKey;
   readonly: boolean;
   value?: string;
index fa28597c55784d585fcec7231f0aea69e298bee1..7c662d9dead4aa46e519b2519d1e560c774615b8 100644 (file)
@@ -1,7 +1,7 @@
 import Constants from '../utils/Constants';
 import { WorkerData } from '../types/Worker';
 
-export default abstract class WorkerAbstract {
+export default abstract class WorkerAbstract<T extends WorkerData> {
   protected readonly workerScript: string;
   protected readonly workerStartDelay: number;
   public abstract readonly size: number;
@@ -20,5 +20,5 @@ export default abstract class WorkerAbstract {
 
   public abstract start(): Promise<void>;
   public abstract stop(): Promise<void>;
-  public abstract addElement(elementData: WorkerData): Promise<void>;
+  public abstract addElement(elementData: T): Promise<void>;
 }
index e2dd3b1e9bdd76cededb0762401561ebca952e75..37b6ddd15c32a71f43a2de51a91af308ce31ac3e 100644 (file)
@@ -6,7 +6,7 @@ import WorkerAbstract from './WorkerAbstract';
 import { WorkerData } from '../types/Worker';
 import { WorkerUtils } from './WorkerUtils';
 
-export default class WorkerDynamicPool<T> extends WorkerAbstract {
+export default class WorkerDynamicPool extends WorkerAbstract<WorkerData> {
   private readonly pool: DynamicThreadPool<WorkerData>;
 
   /**
@@ -56,7 +56,7 @@ export default class WorkerDynamicPool<T> extends WorkerAbstract {
    * @returns
    * @public
    */
-  public async addElement(elementData: T): Promise<void> {
+  public async addElement(elementData: WorkerData): Promise<void> {
     await this.pool.execute(elementData);
     // Start worker sequentially to optimize memory at startup
     await Utils.sleep(this.workerStartDelay);
index 8b979d06ca2d76f8e4d0220132b03dafd8a6bf84..76da7a20fc453d561218814be001e53a43a7c546 100644 (file)
@@ -1,5 +1,5 @@
 import { Worker, isMainThread } from 'worker_threads';
-import { WorkerOptions, WorkerProcessType } from '../types/Worker';
+import { WorkerData, WorkerOptions, WorkerProcessType } from '../types/Worker';
 
 import Constants from '../utils/Constants';
 import { PoolOptions } from 'poolifier';
@@ -13,7 +13,7 @@ export default class WorkerFactory {
     // This is intentional
   }
 
-  public static getWorkerImplementation<T>(workerScript: string, workerProcessType: WorkerProcessType, options?: WorkerOptions): WorkerAbstract | null {
+  public static getWorkerImplementation<T extends WorkerData>(workerScript: string, workerProcessType: WorkerProcessType, options?: WorkerOptions): WorkerAbstract<T> | null {
     if (!isMainThread) {
       throw new Error('Trying to get a worker implementation outside the main thread');
     }
@@ -21,20 +21,20 @@ export default class WorkerFactory {
     options.startDelay = options?.startDelay ?? Constants.WORKER_START_DELAY;
     options.poolOptions = options?.poolOptions ?? {} as PoolOptions<Worker>;
     options?.messageHandler && (options.poolOptions.messageHandler = options.messageHandler);
-    let workerImplementation: WorkerAbstract = null;
+    let workerImplementation: WorkerAbstract<T> = null;
     switch (workerProcessType) {
       case WorkerProcessType.WORKER_SET:
         options.elementsPerWorker = options.elementsPerWorker ?? Constants.DEFAULT_CHARGING_STATIONS_PER_WORKER;
-        workerImplementation = new WorkerSet<T>(workerScript, options.elementsPerWorker, options.startDelay, options);
+        workerImplementation = new WorkerSet(workerScript, options.elementsPerWorker, options.startDelay, options);
         break;
       case WorkerProcessType.STATIC_POOL:
         options.poolMaxSize = options.poolMaxSize ?? Constants.DEFAULT_WORKER_POOL_MAX_SIZE;
-        workerImplementation = new WorkerStaticPool<T>(workerScript, options.poolMaxSize, options.startDelay, options.poolOptions);
+        workerImplementation = new WorkerStaticPool(workerScript, options.poolMaxSize, options.startDelay, options.poolOptions);
         break;
       case WorkerProcessType.DYNAMIC_POOL:
         options.poolMinSize = options.poolMinSize ?? Constants.DEFAULT_WORKER_POOL_MIN_SIZE;
         options.poolMaxSize = options.poolMaxSize ?? Constants.DEFAULT_WORKER_POOL_MAX_SIZE;
-        workerImplementation = new WorkerDynamicPool<T>(workerScript, options.poolMinSize, options.poolMaxSize, options.startDelay, options.poolOptions);
+        workerImplementation = new WorkerDynamicPool(workerScript, options.poolMinSize, options.poolMaxSize, options.startDelay, options.poolOptions);
         break;
       default:
         throw new Error(`Worker implementation type '${workerProcessType}' not found`);
index d179608ddb829877802538236f0153424a86a1d7..64399c87616b11b488d63a60e45620eca7cb2d3b 100644 (file)
@@ -1,13 +1,13 @@
 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
 
-import { WorkerMessageEvents, WorkerOptions, WorkerSetElement } from '../types/Worker';
+import { WorkerData, WorkerMessageEvents, WorkerOptions, WorkerSetElement } from '../types/Worker';
 
 import Utils from '../utils/Utils';
 import { Worker } from 'worker_threads';
 import WorkerAbstract from './WorkerAbstract';
 import { WorkerUtils } from './WorkerUtils';
 
-export default class WorkerSet<T> extends WorkerAbstract {
+export default class WorkerSet extends WorkerAbstract<WorkerData> {
   public readonly maxElementsPerWorker: number;
   private readonly messageHandler: (message: unknown) => void | Promise<void>;
   private readonly workerSet: Set<WorkerSetElement>;
@@ -37,7 +37,7 @@ export default class WorkerSet<T> extends WorkerAbstract {
    * @returns
    * @public
    */
-  public async addElement(elementData: T): Promise<void> {
+  public async addElement(elementData: WorkerData): Promise<void> {
     if (!this.workerSet) {
       throw new Error('Cannot add a WorkerSet element: workers\' set does not exist');
     }
index d5d6390c7b2e43ad5175fa46b21c41122879e7ed..1cbd18e8b9b1cf89094350c71ac2b1488d2d0aac 100644 (file)
@@ -6,7 +6,7 @@ import WorkerAbstract from './WorkerAbstract';
 import { WorkerData } from '../types/Worker';
 import { WorkerUtils } from './WorkerUtils';
 
-export default class WorkerStaticPool<T> extends WorkerAbstract {
+export default class WorkerStaticPool extends WorkerAbstract<WorkerData> {
   private readonly pool: FixedThreadPool<WorkerData>;
 
   /**
@@ -55,7 +55,7 @@ export default class WorkerStaticPool<T> extends WorkerAbstract {
    * @returns
    * @public
    */
-  public async addElement(elementData: T): Promise<void> {
+  public async addElement(elementData: WorkerData): Promise<void> {
     await this.pool.execute(elementData);
     // Start worker sequentially to optimize memory at startup
     await Utils.sleep(this.workerStartDelay);