Added ChargingProfile create route
MODIFY
src/assets/server/rest/v1/docs/e-mobility-oas.json
src/assets/server/rest/v1/docs/e-mobility-oas.json
"$ref": "#/components/responses/BackendError"
}
}
+ },
+ "post": {
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "description": "Create a Charging Station profile",
+ "tags": [
+ "Charging Profiles"
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ChargingProfile"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Charging station profile created successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string",
+ "example": "Success"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/UnauthorizedError"
+ },
+ "403": {
+ "$ref": "#/components/responses/ForbiddenError"
+ },
+ "500": {
+ "$ref": "#/components/responses/BackendError"
+ },
+ "585": {
+ "$ref": "#/components/responses/NotSupportedError"
+ }
+ }
}
},
"/api/chargingprofiles/{id}": {
"profile"
],
"properties": {
- "id": {
- "type": "string",
- "example": ""
- },
"chargingStationID": {
"type": "string",
"example": ""

MODIFY
src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts
src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts
@@ -35,7 +35,7 @@ export default abstract class ChargingStationVendorIntegration {
const numberOfPhases = Utils.getNumberOfConnectedPhases(chargingStation, chargePoint);
const numberOfConnectors = chargePoint ? chargePoint.connectorIDs.length : chargingStation.connectors.length;
if (chargePoint.excludeFromPowerLimitation) {
- Logging.logWarning({
+ await Logging.logWarning({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_STATION_LIMIT_POWER,
@@ -46,7 +46,7 @@ export default abstract class ChargingStationVendorIntegration {
return { status: OCPPConfigurationStatus.NOT_SUPPORTED };
}
if (!chargePoint.ocppParamForPowerLimitation) {
- Logging.logWarning({
+ await Logging.logWarning({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_STATION_LIMIT_POWER,
@@ -86,7 +86,7 @@ export default abstract class ChargingStationVendorIntegration {
const ocppLimitAmpValue = this.convertLimitAmpPerPhase(chargingStation, chargePoint, 0, maxAmps * ocppParamValueMultiplier);
let result: OCPPChangeConfigurationCommandResult;
try {
- Logging.logDebug({
+ await Logging.logDebug({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_STATION_LIMIT_POWER,
@@ -132,7 +132,7 @@ export default abstract class ChargingStationVendorIntegration {
const connector = Utils.getConnectorFromID(chargingStation, connectorID);
if (connector) {
connector.amperageLimit = this.convertLimitAmpToAllPhases(chargingStation, chargePoint, connectorID, Utils.convertToInt(ocppParamValue) / ocppParamValueDivider);
- Logging.logInfo({
+ await Logging.logInfo({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.OCPP_PARAM_UPDATE,
@@ -186,7 +186,7 @@ export default abstract class ChargingStationVendorIntegration {
});
// Call each connector?
if (result.status !== OCPPChargingProfileStatus.ACCEPTED) {
- Logging.logWarning({
+ await Logging.logWarning({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_PROFILE_UPDATE,
@@ -213,7 +213,7 @@ export default abstract class ChargingStationVendorIntegration {
});
return result;
} catch (error) {
- Logging.logError({
+ await Logging.logError({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_PROFILE_UPDATE,
@@ -260,7 +260,7 @@ export default abstract class ChargingStationVendorIntegration {
});
// Call each connector?
if (result.status !== OCPPClearChargingProfileStatus.ACCEPTED) {
- Logging.logWarning({
+ await Logging.logWarning({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_PROFILE_DELETE,
@@ -303,7 +303,7 @@ export default abstract class ChargingStationVendorIntegration {
}
return result;
} catch (error) {
- Logging.logError({
+ await Logging.logError({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_PROFILE_DELETE,
@@ -355,7 +355,7 @@ export default abstract class ChargingStationVendorIntegration {
result.chargingSchedule = this.convertFromVendorChargingSchedule(chargingStation, chargePoint, result.connectorId, result.chargingSchedule);
return result;
} catch (error) {
- Logging.logError({
+ await Logging.logError({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_STATION_GET_COMPOSITE_SCHEDULE,
@@ -442,7 +442,7 @@ export default abstract class ChargingStationVendorIntegration {
limitWatts: Utils.convertAmpToWatt(chargingStation, chargePoint, connectorID, connectorLimitAmps),
limitSource: ConnectorCurrentLimitSource.STATIC_LIMITATION,
};
- Logging.logInfo({
+ await Logging.logInfo({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.GET_CONNECTOR_CURRENT_LIMIT,
@@ -454,7 +454,7 @@ export default abstract class ChargingStationVendorIntegration {
}
}
} catch (error) {
- Logging.logError({
+ await Logging.logError({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.GET_CONNECTOR_CURRENT_LIMIT,
@@ -469,7 +469,7 @@ export default abstract class ChargingStationVendorIntegration {
limitWatts: limitDefaultMaxPower,
limitSource: ConnectorCurrentLimitSource.CONNECTOR
};
- Logging.logInfo({
+ await Logging.logInfo({
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.GET_CONNECTOR_CURRENT_LIMIT,

MODIFY
src/integration/smart-charging/SmartChargingIntegration.ts
src/integration/smart-charging/SmartChargingIntegration.ts
@@ -31,7 +31,7 @@ export default abstract class SmartChargingIntegration<T extends SmartChargingSe
// Call the charging plans
const chargingProfiles: ChargingProfile[] = await this.buildChargingProfiles(siteArea, this.excludedChargingStations);
if (!chargingProfiles) {
- Logging.logInfo({
+ await Logging.logInfo({
tenantID: this.tenantID,
action: ServerAction.CHARGING_PROFILE_UPDATE,
module: MODULE_NAME, method: 'computeAndApplyChargingProfiles',
@@ -53,7 +53,7 @@ export default abstract class SmartChargingIntegration<T extends SmartChargingSe
}
actionsResponse.inError++;
// Log failed
- Logging.logError({
+ await Logging.logError({
tenantID: this.tenantID,
source: chargingProfile.chargingStationID,
action: ServerAction.CHARGING_PROFILE_UPDATE,
@@ -64,7 +64,7 @@ export default abstract class SmartChargingIntegration<T extends SmartChargingSe
}
}
// Log
- Logging.logActionsResponse(this.tenantID, ServerAction.CHECK_AND_APPLY_SMART_CHARGING,
+ await Logging.logActionsResponse(this.tenantID, ServerAction.CHECK_AND_APPLY_SMART_CHARGING,
MODULE_NAME, 'computeAndApplyChargingProfiles', actionsResponse,
'{{inSuccess}} charging plan(s) were successfully pushed',
'{{inError}} charging plan(s) failed to be pushed',
@@ -122,7 +122,7 @@ export default abstract class SmartChargingIntegration<T extends SmartChargingSe
return true;
} catch (error) {
// Log failed
- Logging.logError({
+ await Logging.logError({
tenantID: this.tenantID,
source: chargingProfile.chargingStationID,
action: ServerAction.CHARGING_PROFILE_UPDATE,

MODIFY
src/server/rest/v1/router/api/ChargingStationRouter.ts
src/server/rest/v1/router/api/ChargingStationRouter.ts
@@ -21,6 +21,7 @@ export default class ChargingStationRouter {
this.buildRouteChargingStationDownloadFirmware();
this.buildRouteChargingStationDeleteChargingProfile();
this.buildRouteChargingStationUpdateChargingProfile();
+ this.buildRouteChargingStationCreateChargingProfile();
this.buildRouteChargingStationChangeAvailability();
this.buildRouteChargingStationTransactions();
this.buildRouteChargingStations();
@@ -238,6 +239,12 @@ export default class ChargingStationRouter {
});
}
+ protected buildRouteChargingStationCreateChargingProfile(): void {
+ this.router.post(`/${ServerRoute.REST_CHARGING_PROFILES}`, async (req: Request, res: Response, next: NextFunction) => {
+ await RouterUtils.handleServerAction(ChargingStationService.handleCreateChargingProfile.bind(this), ServerAction.CHARGING_PROFILE_CREATE, req, res, next);
+ });
+ }
+
protected buildRouteChargingStationUpdateChargingProfile(): void {
this.router.put(`/${ServerRoute.REST_CHARGING_PROFILE}`, async (req: Request, res: Response, next: NextFunction) => {
await RouterUtils.handleServerAction(ChargingStationService.handleUpdateChargingProfile.bind(this), ServerAction.CHARGING_PROFILE_UPDATE, req, res, next);

MODIFY
src/server/rest/v1/service/ChargingStationService.ts
src/server/rest/v1/service/ChargingStationService.ts
@@ -607,6 +607,63 @@ export default class ChargingStationService {
next();
}
+ public static async handleCreateChargingProfile(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Filter
+ const filteredRequest = ChargingStationSecurity.filterChargingProfileCreateRequest(req.body);
+ // Check existence
+ const chargingStation = await ChargingStationStorage.getChargingStation(req.user.tenantID, filteredRequest.chargingStationID);
+ UtilsService.assertObjectExists(action, chargingStation, `ChargingStation '${filteredRequest.chargingStationID}' does not exist.`,
+ MODULE_NAME, 'handleCreateChargingProfile', req.user);
+ // OCPI Charging Station
+ if (!chargingStation.issuer) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `Charging Station '${chargingStation.id}' not issued by the organization`,
+ module: MODULE_NAME, method: 'handleCreateChargingProfile',
+ user: req.user,
+ action: action
+ });
+ }
+ const chargePoint = Utils.getChargePointFromID(chargingStation, filteredRequest.chargePointID);
+ UtilsService.assertObjectExists(action, chargePoint, `Charge Point '${filteredRequest.chargePointID}' does not exist.`,
+ MODULE_NAME, 'handleCreateChargingProfile', req.user);
+ // Check Mandatory fields
+ UtilsService.checkIfChargingProfileIsValid(chargingStation, chargePoint, filteredRequest, req);
+ let siteID = null;
+ if (Utils.isComponentActiveFromToken(req.user, TenantComponents.ORGANIZATION)) {
+ // Get the Site Area
+ const siteArea = await SiteAreaStorage.getSiteArea(req.user.tenantID, chargingStation.siteAreaID);
+ siteID = siteArea ? siteArea.siteID : null;
+ }
+ // Check Auth
+ if (!Authorizations.canUpdateChargingStation(req.user, siteID)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: req.user,
+ action: Action.UPDATE, entity: Entity.CHARGING_STATION,
+ module: MODULE_NAME, method: 'handleCreateChargingProfile',
+ value: chargingStation.id
+ });
+ }
+ // Check if Charging Profile is supported
+ if (!chargingStation.capabilities || !chargingStation.capabilities.supportChargingProfiles) {
+ throw new AppError({
+ source: chargingStation.id,
+ action: action,
+ errorCode: HTTPError.FEATURE_NOT_SUPPORTED_ERROR,
+ user: req.user,
+ module: MODULE_NAME, method: 'handleCreateChargingProfile',
+ message: `Charging Station '${chargingStation.id}' does not support Charging Profiles`,
+ });
+ }
+ // Apply & Save charging plan
+ const chargingProfileID = await OCPPUtils.setAndSaveChargingProfile(req.user.tenantID, filteredRequest, req.user);
+ // Ok
+ res.status(StatusCodes.CREATED).json(Object.assign({ id: chargingProfileID }, Constants.REST_RESPONSE_SUCCESS));
+ next();
+ }
+
public static async handleUpdateChargingProfile(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const filteredRequest = ChargingStationSecurity.filterChargingProfileUpdateRequest({ ...req.params, ...req.body });

MODIFY
src/server/rest/v1/service/security/ChargingStationSecurity.ts
src/server/rest/v1/service/security/ChargingStationSecurity.ts
@@ -194,6 +194,22 @@ export default class ChargingStationSecurity {
return filteredRequest;
}
+ public static filterChargingProfileCreateRequest(request: any): ChargingProfile {
+ const filteredRequest: ChargingProfile = {} as ChargingProfile;
+ if (Utils.objectHasProperty(request, 'chargingStationID')) {
+ filteredRequest.chargingStationID = sanitize(request.chargingStationID);
+ }
+ if (Utils.objectHasProperty(request, 'connectorID')) {
+ filteredRequest.connectorID = sanitize(request.connectorID);
+ }
+ if (Utils.objectHasProperty(request, 'chargePointID')) {
+ filteredRequest.chargePointID = sanitize(request.chargePointID);
+ }
+ if (Utils.objectHasProperty(request, 'profile')) {
+ filteredRequest.profile = ChargingStationSecurity.filterChargingProfileRequest(request.profile);
+ }
+ return filteredRequest;
+ }
public static filterChargingProfileUpdateRequest(request: any): ChargingProfile {
const filteredRequest: ChargingProfile = {} as ChargingProfile;

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -73,6 +73,7 @@ export enum ServerAction {
CHARGING_PROFILES = 'ChargingProfiles',
CHARGING_PROFILE_DELETE = 'ChargingProfileDelete',
CHARGING_PROFILE_UPDATE = 'ChargingProfileUpdate',
+ CHARGING_PROFILE_CREATE = 'ChargingProfileCreate',
GENERATE_QR_CODE_FOR_CONNECTOR = 'GenerateQrCodeForConnector',
OCPP_PARAM_UPDATE = 'OCPPParamUpdate',
RESEND_VERIFICATION_MAIL = 'ResendVerificationEmail',

Added ChargingProfile create route