Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 70: Generalize Boot #322

Open
wants to merge 3 commits into
base: rc-1.6.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions 00_Base/src/config/BootConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
//
// SPDX-License-Identifier: Apache 2.0

import { OCPP2_0_1 } from '../ocpp/model';

export interface BootConfig {
/**
* Also declared in SystemConfig. If absent, SystemConfig value is used.
Expand All @@ -14,8 +12,8 @@ export interface BootConfig {
* Also declared in SystemConfig. If absent, SystemConfig value is used.
*/
bootRetryInterval?: number;
status: OCPP2_0_1.RegistrationStatusEnumType;
statusInfo?: OCPP2_0_1.StatusInfoType | null;
status: string;
statusInfo?: object | null;
/**
* Also declared in SystemConfig. If absent, SystemConfig value is used.
*/
Expand Down
11 changes: 11 additions & 0 deletions 01_Data/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const jestConfig: JestConfigWithTsJest = {
testEnvironment: 'node',
transform: {
'^.+.tsx?$': ['ts-jest', {}],
},
displayName: 'Data Module',
};

export default jestConfig;
2 changes: 1 addition & 1 deletion 01_Data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"scripts": {
"prepublish": "npx eslint",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest --config jest.config.ts"
},
"keywords": [
"ocpp",
Expand Down
1 change: 1 addition & 0 deletions 01_Data/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@ export {
SequelizeTransactionEventRepository,
SequelizeVariableMonitoringRepository,
SequelizeChargingStationSequenceRepository,
OCPP2_0_1_Mapper,
} from './layers/sequelize'; // TODO ensure all needed modules are properly exported
export { RepositoryStore } from './layers/sequelize/repository/RepositoryStore';
21 changes: 8 additions & 13 deletions 01_Data/src/interfaces/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
//
// SPDX-License-Identifier: Apache 2.0

import {
type BootConfig,
type CallAction,
ChargingStationSequenceType,
type CrudRepository,
OCPP2_0_1,
} from '@citrineos/base';
import { type BootConfig, type CallAction, ChargingStationSequenceType, type CrudRepository, OCPP2_0_1 } from '@citrineos/base';
import { type AuthorizationQuerystring } from './queries/Authorization';
import {
type Authorization,
Expand Down Expand Up @@ -38,12 +32,12 @@ import {
type VariableAttribute,
VariableCharacteristics,
type VariableMonitoring,
LocalListVersion,
SendLocalList,
InstalledCertificate,
} from '../layers/sequelize';
import { type AuthorizationRestrictions, type VariableAttributeQuerystring } from '.';
import { TariffQueryString } from './queries/Tariff';
import { LocalListVersion } from '../layers/sequelize/model/Authorization/LocalListVersion';
import { SendLocalList } from '../layers/sequelize/model/Authorization/SendLocalList';
import { InstalledCertificate } from '../layers/sequelize/model/Certificate/InstalledCertificate';

export interface IAuthorizationRepository extends CrudRepository<OCPP2_0_1.AuthorizationData> {
createOrUpdateByQuerystring: (value: OCPP2_0_1.AuthorizationData, query: AuthorizationQuerystring) => Promise<Authorization | undefined>;
Expand Down Expand Up @@ -86,6 +80,7 @@ export interface ILocalAuthListRepository extends CrudRepository<LocalListVersio
/**
* Creates a SendLocalList.
* @param {string} stationId - The ID of the station.
* @param correlationId - The correlation ID.
* @param {UpdateEnumType} updateType - The type of update.
* @param {number} versionNumber - The version number.
* @param {AuthorizationData[]} localAuthorizationList - The list of authorizations.
Expand Down Expand Up @@ -165,7 +160,7 @@ export interface ICertificateRepository extends CrudRepository<Certificate> {
createOrUpdateCertificate(certificate: Certificate): Promise<Certificate>;
}

export interface IInstalledCertificateRepository extends CrudRepository<InstalledCertificate> { }
export interface IInstalledCertificateRepository extends CrudRepository<InstalledCertificate> {}

export interface IChargingProfileRepository extends CrudRepository<ChargingProfile> {
createOrUpdateChargingProfile(chargingProfile: OCPP2_0_1.ChargingProfileType, stationId: string, evseId?: number | null, chargingLimitSource?: OCPP2_0_1.ChargingLimitSourceEnumType, isActive?: boolean): Promise<ChargingProfile>;
Expand All @@ -181,7 +176,7 @@ export interface IReservationRepository extends CrudRepository<Reservation> {
createOrUpdateReservation(reserveNowRequest: OCPP2_0_1.ReserveNowRequest, stationId: string, isActive?: boolean): Promise<Reservation | undefined>;
}

export interface ICallMessageRepository extends CrudRepository<CallMessage> { }
export interface ICallMessageRepository extends CrudRepository<CallMessage> {}

export interface IChargingStationSecurityInfoRepository extends CrudRepository<ChargingStationSecurityInfo> {
readChargingStationPublicKeyFileId(stationId: string): Promise<string>;
Expand All @@ -192,4 +187,4 @@ export interface IChargingStationSequenceRepository extends CrudRepository<Charg
getNextSequenceValue(stationId: string, type: ChargingStationSequenceType): Promise<number>;
}

export interface IServerNetworkProfileRepository extends CrudRepository<ServerNetworkProfile> { }
export interface IServerNetworkProfileRepository extends CrudRepository<ServerNetworkProfile> {}
3 changes: 3 additions & 0 deletions 01_Data/src/layers/sequelize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ export { SequelizeChargingStationSequenceRepository } from './repository/Chargin

// Sequelize Utilities
export { DefaultSequelizeInstance } from './util';

// Sequelize Mappers
export * as OCPP2_0_1_Mapper from './mapper/2.0.1';
57 changes: 57 additions & 0 deletions 01_Data/src/layers/sequelize/mapper/2.0.1/BootMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { OCPP2_0_1 } from '@citrineos/base';
import { AbstractMapper } from '../AbstractMapper';
import { Boot } from '../../model/Boot';

export class BootMapper extends AbstractMapper {
lastBootTime?: string;
heartbeatInterval?: number;
bootRetryInterval?: number;
status: OCPP2_0_1.RegistrationStatusEnumType;
statusInfo?: OCPP2_0_1.StatusInfoType;
getBaseReportOnPending?: boolean;
pendingBootSetVariables?: OCPP2_0_1.VariableAttributeType[];
variablesRejectedOnLastBoot: OCPP2_0_1.SetVariableResultType[];
bootWithRejectedVariables?: boolean;
customData?: OCPP2_0_1.CustomDataType;

constructor(boot: Boot) {
super();
try {
this.status = boot.status as OCPP2_0_1.RegistrationStatusEnumType;
this.variablesRejectedOnLastBoot = boot.variablesRejectedOnLastBoot as OCPP2_0_1.SetVariableResultType[];
this.fromModel(boot);
} catch (error) {
throw new Error(`Generate BootMapper from Boot failed: ${error}`);
}
}

toModel(): Boot {
try {
return {
lastBootTime: this.lastBootTime,
heartbeatInterval: this.heartbeatInterval,
bootRetryInterval: this.bootRetryInterval,
status: this.status,
statusInfo: this.statusInfo,
getBaseReportOnPending: this.getBaseReportOnPending,
pendingBootSetVariables: this.pendingBootSetVariables,
variablesRejectedOnLastBoot: this.variablesRejectedOnLastBoot,
bootWithRejectedVariables: this.bootWithRejectedVariables,
customData: this.customData,
} as Boot;
} catch (error) {
throw new Error('Convert BootMapper to Boot failed: ' + error);
}
}

private fromModel(boot: Boot): void {
this.lastBootTime = boot.lastBootTime ? boot.lastBootTime : undefined;
this.heartbeatInterval = boot.heartbeatInterval;
this.bootRetryInterval = boot.bootRetryInterval;
this.statusInfo = boot.statusInfo != null ? (boot.statusInfo as OCPP2_0_1.StatusInfoType) : undefined;
this.getBaseReportOnPending = boot.getBaseReportOnPending;
this.pendingBootSetVariables = boot.pendingBootSetVariables ? (boot.pendingBootSetVariables as OCPP2_0_1.VariableAttributeType[]) : undefined;
this.bootWithRejectedVariables = boot.bootWithRejectedVariables;
this.customData = boot.customData != null ? (boot.customData as OCPP2_0_1.CustomDataType) : undefined;
}
}
6 changes: 6 additions & 0 deletions 01_Data/src/layers/sequelize/mapper/2.0.1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2023 S44, LLC
// Copyright Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache 2.0

export { BootMapper } from './BootMapper';
5 changes: 5 additions & 0 deletions 01_Data/src/layers/sequelize/mapper/AbstractMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Model } from 'sequelize-typescript';

export abstract class AbstractMapper {
abstract toModel(): Model;
}
10 changes: 5 additions & 5 deletions 01_Data/src/layers/sequelize/model/Boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// SPDX-License-Identifier: Apache 2.0

import { type BootConfig, Namespace, OCPP2_0_1 } from '@citrineos/base';
import { type BootConfig, Namespace } from '@citrineos/base';
import { Column, DataType, HasMany, Model, PrimaryKey, Table } from 'sequelize-typescript';
import { VariableAttribute } from './DeviceModel';

Expand Down Expand Up @@ -34,10 +34,10 @@ export class Boot extends Model implements BootConfig {
declare bootRetryInterval?: number;

@Column(DataType.STRING)
declare status: OCPP2_0_1.RegistrationStatusEnumType;
declare status: string;

@Column(DataType.JSON)
declare statusInfo?: OCPP2_0_1.StatusInfoType;
declare statusInfo?: object;

@Column(DataType.BOOLEAN)
declare getBaseReportOnPending?: boolean;
Expand All @@ -49,10 +49,10 @@ export class Boot extends Model implements BootConfig {
declare pendingBootSetVariables?: VariableAttribute[];

@Column(DataType.JSON)
declare variablesRejectedOnLastBoot: OCPP2_0_1.SetVariableResultType[];
declare variablesRejectedOnLastBoot: object[];

@Column(DataType.BOOLEAN)
declare bootWithRejectedVariables?: boolean;

declare customData?: OCPP2_0_1.CustomDataType;
declare customData?: object;
}
4 changes: 2 additions & 2 deletions 01_Data/src/layers/sequelize/repository/Boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// SPDX-License-Identifier: Apache 2.0

import { CrudRepository, SystemConfig, type BootConfig, OCPP2_0_1 } from '@citrineos/base';
import { CrudRepository, SystemConfig, type BootConfig } from '@citrineos/base';
import { type IBootRepository } from '../../../interfaces';
import { Boot } from '../model/Boot';
import { VariableAttribute } from '../model/DeviceModel';
Expand Down Expand Up @@ -53,7 +53,7 @@ export class SequelizeBootRepository extends SequelizeRepository<Boot> implement
return savedBootConfig;
}

async updateStatusByKey(status: OCPP2_0_1.RegistrationStatusEnumType, statusInfo: OCPP2_0_1.StatusInfoType | undefined, key: string): Promise<Boot | undefined> {
async updateStatusByKey(status: string, statusInfo: object | undefined, key: string): Promise<Boot | undefined> {
return await this.updateByKey({ status, statusInfo }, key);
}

Expand Down
60 changes: 60 additions & 0 deletions 01_Data/test/layers/sequelize/mapper/2.0.1/BootMapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect } from '@jest/globals';
import { Boot, VariableAttribute } from '../../../../../src';
import { faker } from '@faker-js/faker';
import { OCPP2_0_1 } from '@citrineos/base';
import { BootMapper } from '../../../../../src/layers/sequelize/mapper/2.0.1';

describe('BootMapper', () => {
describe('map Boot and BootMapper', () => {
it('should be equal after mapping', () => {
const givenSetVariable: OCPP2_0_1.SetVariableResultType = {
attributeStatus: OCPP2_0_1.SetVariableStatusEnumType.Accepted,
component: {
name: faker.lorem.word(),
instance: faker.string.uuid(),
},
variable: {
name: faker.lorem.word(),
instance: faker.string.uuid(),
},
customData: {
vendorId: faker.string.uuid(),
},
attributeType: OCPP2_0_1.AttributeEnumType.Actual,
attributeStatusInfo: {
reasonCode: faker.lorem.word(),
additionalInfo: faker.lorem.sentence(),
},
};
const givenBoot: Boot = {
lastBootTime: faker.date.recent().toISOString(),
heartbeatInterval: faker.number.int({ min: 30, max: 3600 }),
bootRetryInterval: faker.number.int({ min: 30, max: 3600 }),
status: 'Pending',
getBaseReportOnPending: false,
variablesRejectedOnLastBoot: [givenSetVariable],
pendingBootSetVariables: [
{
stationId: faker.string.uuid(),
evseDatabaseId: faker.number.int({ min: 0, max: 100 }),
dataType: OCPP2_0_1.DataEnumType.string,
type: OCPP2_0_1.AttributeEnumType.Actual,
value: 'test',
mutability: OCPP2_0_1.MutabilityEnumType.ReadWrite,
persistent: false,
constant: false,
generatedAt: faker.date.recent().toISOString(),
variableId: faker.number.int({ min: 0, max: 100 }),
componentId: faker.number.int({ min: 0, max: 100 }),
} as VariableAttribute,
],
bootWithRejectedVariables: faker.datatype.boolean(),
} as Boot;

const actualMapper = new BootMapper(givenBoot);

expect(actualMapper).toBeTruthy();
expect(actualMapper.toModel()).toEqual(givenBoot);
});
});
});
Loading
Loading