Push OICP EVSE (Status) Task
MODIFY
src/assets/config-template-http.json
src/assets/config-template-http.json
"debug": false,
"externalProtocol": "http"
},
+ "OICPService": {
+ "protocol": "http",
+ "host": "localhost",
+ "port": 9080,
+ "debug": false,
+ "externalProtocol": "http"
+ },
"CentralSystemServer": {
"protocol": "http",
"host": "localhost",
},
"Scheduler": {
"active": false,
- "tasks": []
+ "tasks": [
+ {
+ "name": "OICPPushEVSEDataTask",
+ "active": false,
+ "periodicity": "*/10 * * * *",
+ "config": {
+ "processAllEVSEs": true
+ }
+ },
+ {
+ "name": "OICPPushEvseStatusTask",
+ "active": false,
+ "periodicity": "*/5 * * * *",
+ "config": {
+ "processAllEVSEs": true
+ }
+ }
+ ]
},
"Logging": {
"logLevel": "D",

MODIFY
src/assets/config-template-https.json
src/assets/config-template-https.json
"debug": false,
"externalProtocol": "https"
},
+ "OICPService": {
+ "protocol": "https",
+ "host": "localhost",
+ "port": 9080,
+ "ssl-key": "",
+ "ssl-cert": "",
+ "ssl-ca": [],
+ "debug": false,
+ "externalProtocol": "https"
+ },
"CentralSystemServer": {
"protocol": "https",
"host": "localhost",
},
"Scheduler": {
"active": false,
- "tasks": []
+ "tasks": [
+ {
+ "name": "OICPPushEVSEDataTask",
+ "active": false,
+ "periodicity": "*/10 * * * *",
+ "config": {
+ "processAllEVSEs": true
+ }
+ },
+ {
+ "name": "OICPPushEvseStatusTask",
+ "active": false,
+ "periodicity": "*/5 * * * *",
+ "config": {
+ "processAllEVSEs": true
+ }
+ }]
},
"Logging": {
"logLevel": "D",

MODIFY
src/client/oicp/CpoOICPClient.ts
src/client/oicp/CpoOICPClient.ts
@@ -208,6 +208,9 @@ export default class CpoOICPClient extends OICPClient {
if (!actionType) {
actionType = OICPActionType.fullLoad;
}
+ if (!processAllEVSEs) {
+ actionType = OICPActionType.insert;
+ }
// Result
const result = {
success: 0,
@@ -221,8 +224,8 @@ export default class CpoOICPClient extends OICPClient {
// Define get option
const options = {
addChargeBoxID: true,
- countryID: this.getLocalCountryCode(ServerAction.OICP_PUSH_EVSES),
- partyID: this.getLocalPartyID(ServerAction.OICP_PUSH_EVSES)
+ countryID: this.getLocalCountryCode(ServerAction.OICP_PUSH_EVSE_DATA),
+ partyID: this.getLocalPartyID(ServerAction.OICP_PUSH_EVSE_DATA)
};
// Get timestamp before starting process - to be saved in DB at the end of the process
const startDate = new Date();
@@ -311,7 +314,7 @@ export default class CpoOICPClient extends OICPClient {
// Save
const executionDurationSecs = (new Date().getTime() - startTime) / 1000;
await OICPEndpointStorage.saveOicpEndpoint(this.tenant.id, this.oicpEndpoint);
- Logging.logOicpResult(this.tenant.id, ServerAction.OICP_PUSH_EVSES,
+ Logging.logOicpResult(this.tenant.id, ServerAction.OICP_PUSH_EVSE_DATA,
MODULE_NAME, 'sendEVSEs', result,
`{{inSuccess}} EVSE(s) were successfully patched in ${executionDurationSecs}s`,
`{{inError}} EVSE(s) failed to be patched in ${executionDurationSecs}s`,
@@ -328,6 +331,9 @@ export default class CpoOICPClient extends OICPClient {
if (!actionType) {
actionType = OICPActionType.fullLoad;
}
+ if (!processAllEVSEs) {
+ actionType = OICPActionType.insert;
+ }
// Result
const result = {
success: 0,
@@ -487,7 +493,7 @@ export default class CpoOICPClient extends OICPClient {
// Check for input parameter
if (!evses) {
throw new BackendError({
- action: ServerAction.OICP_PUSH_EVSES,
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
message: 'Invalid parameters',
module: MODULE_NAME, method: 'pushEvseData',
});
@@ -495,11 +501,11 @@ export default class CpoOICPClient extends OICPClient {
// Get EVSE endpoint url
// for QA - environment: https://service-qa.hubject.com/api/oicp/evsepush/v23/operators/{operatorID}/data-records
// for PROD - environment: https://service.hubject.com/api/oicp/evsepush/v23/operators/{operatorID}/data-records
- const fullUrl = this.getEndpointUrl('evses', ServerAction.OICP_PUSH_EVSES);
+ const fullUrl = this.getEndpointUrl('evses', ServerAction.OICP_PUSH_EVSE_DATA);
// Build payload
const operatorEvseData: OICPOperatorEvseData = {} as OICPOperatorEvseData;
- operatorEvseData.OperatorID = this.getOperatorID(ServerAction.OICP_PUSH_EVSES);
+ operatorEvseData.OperatorID = this.getOperatorID(ServerAction.OICP_PUSH_EVSE_DATA);
operatorEvseData.OperatorName = this.tenant.name;
operatorEvseData.EvseDataRecord = evses;
@@ -510,7 +516,7 @@ export default class CpoOICPClient extends OICPClient {
// Log
Logging.logDebug({
tenantID: this.tenant.id,
- action: ServerAction.OICP_PUSH_EVSES,
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
message: `Push EVSEs from tenant: ${this.tenant.id}`,
module: MODULE_NAME, method: 'pushEvseData',
detailedMessages: { payload }
@@ -1160,7 +1166,7 @@ export default class CpoOICPClient extends OICPClient {
// Check response
if (!response.Result || !(response.StatusCode.Code === OICPCode.Code000)) {
pingResult.statusCode = StatusCodes.PRECONDITION_FAILED;
- pingResult.statusText = `Invalid response from POST ${this.getEndpointUrl('evses',ServerAction.OICP_PUSH_EVSES)}`;
+ pingResult.statusText = `Invalid response from POST ${this.getEndpointUrl('evses',ServerAction.OICP_PUSH_EVSE_DATA)}`;
} else {
pingResult.statusCode = response.StatusCode.Code;
pingResult.statusText = response.StatusCode.Description;
@@ -1179,8 +1185,8 @@ export default class CpoOICPClient extends OICPClient {
async pingEvseEndpoint() {
Logging.logInfo({
tenantID: this.tenant.id,
- action: ServerAction.OICP_PUSH_EVSES,
- message: `Ping Hubject at ${this.getEndpointUrl('evses',ServerAction.OICP_PUSH_EVSES)}`,
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
+ message: `Ping Hubject at ${this.getEndpointUrl('evses',ServerAction.OICP_PUSH_EVSE_DATA)}`,
module: MODULE_NAME, method: 'pingEvseEndpoint'
});
const response = await this.pushEvseData([], OICPActionType.fullLoad);

MODIFY
src/locking/LockingHelper.ts
src/locking/LockingHelper.ts
@@ -110,4 +110,12 @@ export default class LockingHelper {
public static async createOCPIPatchCpoLocationsLock(tenantID: string, ocpiEndpoint: OCPIEndpoint): Promise<Lock|null> {
return LockingHelper.createOCPIEndpointActionLock(tenantID, ocpiEndpoint, 'patch-cpo-locations');
}
+
+ public static async createOICPPatchCpoEVSEsLock(tenantID: string, oicpEndpoint: OICPEndpoint): Promise<Lock|null> {
+ return LockingHelper.createOICPEndpointActionLock(tenantID, oicpEndpoint, 'patch-cpo-evse-data');
+ }
+
+ public static async createOICPPatchCpoEvseStatusesLock(tenantID: string, oicpEndpoint: OICPEndpoint): Promise<Lock|null> {
+ return LockingHelper.createOICPEndpointActionLock(tenantID, oicpEndpoint, 'patch-cpo-evse-statuses');
+ }
}

MODIFY
src/scheduler/SchedulerManager.ts
src/scheduler/SchedulerManager.ts
@@ -18,6 +18,8 @@ import OCPIGetSessionsTask from './tasks/ocpi/OCPIGetSessionsTask';
import OCPIGetTokensTask from './tasks/ocpi/OCPIGetTokensTask';
import OCPIPushCdrsTask from './tasks/ocpi/OCPIPushCdrsTask';
import OCPIPushLocationsTask from './tasks/ocpi/OCPIPushLocationsTask';
+import OICPPushEvseDataTask from './tasks/oicp/OICPPushEvseDataTask';
+import OICPPushEvseStatusTask from './tasks/oicp/OICPPushEVSEStatusTask';
import SchedulerTask from './SchedulerTask';
import { ServerAction } from '../types/Server';
import SynchronizeBillingInvoicesTask from './tasks/SynchronizeBillingInvoicesTask';
@@ -71,6 +73,12 @@ export default class SchedulerManager {
// The task runs every five minutes
schedulerTask = new CheckPreparingSessionNotStartedTask();
break;
+ case 'OICPPushEVSEDataTask':
+ schedulerTask = new OICPPushEvseDataTask();
+ break;
+ case 'OICPPushEvseStatusTask':
+ schedulerTask = new OICPPushEvseStatusTask();
+ break;
case 'OCPIPushLocationsTask':
schedulerTask = new OCPIPushLocationsTask();
break;

ADD
src/scheduler/tasks/oicp/OICPPushEvseDataTask.ts
+import { OICPPushEvseDataTaskConfig, TaskConfig } from '../../../types/TaskConfig';
+
+import Constants from '../../../utils/Constants';
+import LockingHelper from '../../../locking/LockingHelper';
+import LockingManager from '../../../locking/LockingManager';
+import Logging from '../../../utils/Logging';
+import OICPClientFactory from '../../../client/oicp/OICPClientFactory';
+import OICPEndpoint from '../../../types/oicp/OICPEndpoint';
+import OICPEndpointStorage from '../../../storage/mongodb/OICPEndpointStorage';
+import { OICPRegistrationStatus } from '../../../types/oicp/OICPRegistrationStatus';
+import { OICPRole } from '../../../types/oicp/OICPRole';
+import SchedulerTask from '../../SchedulerTask';
+import { ServerAction } from '../../../types/Server';
+import Tenant from '../../../types/Tenant';
+import TenantComponents from '../../../types/TenantComponents';
+import Utils from '../../../utils/Utils';
+
+const MODULE_NAME = 'OICPPushEvseDataTask';
+
+export default class OICPPushEvseDataTask extends SchedulerTask {
+
+ async processTenant(tenant: Tenant, config: TaskConfig): Promise<void> {
+ try {
+ // Check if OICP component is active
+ if (Utils.isTenantComponentActive(tenant, TenantComponents.OICP)) {
+ // Get all available endpoints
+ const oicpEndpoints = await OICPEndpointStorage.getOicpEndpoints(tenant.id, { role: OICPRole.CPO }, Constants.DB_PARAMS_MAX_LIMIT);
+ for (const oicpEndpoint of oicpEndpoints.result) {
+ await this.processOICPEndpoint(tenant, oicpEndpoint, config);
+ }
+ }
+ } catch (error) {
+ // Log error
+ Logging.logActionExceptionMessage(tenant.id, ServerAction.OICP_PUSH_EVSE_DATA, error);
+ }
+ }
+
+ private async processOICPEndpoint(tenant: Tenant, oicpEndpoint: OICPEndpoint, config: OICPPushEvseDataTaskConfig): Promise<void> {
+ // Get the lock
+ const oicpLock = await LockingHelper.createOICPPatchCpoEVSEsLock(tenant.id, oicpEndpoint);
+ if (oicpLock) {
+ try {
+ // Check if OICP endpoint is registered
+ if (oicpEndpoint.status !== OICPRegistrationStatus.REGISTERED) {
+ Logging.logDebug({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
+ message: `The OICP Endpoint ${oicpEndpoint.name} is not registered. Skipping the oicpendpoint.`
+ });
+ return;
+ } else if (!oicpEndpoint.backgroundPatchJob) {
+ Logging.logDebug({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
+ message: `The OICP Endpoint ${oicpEndpoint.name} is inactive.`
+ });
+ return;
+ }
+ Logging.logInfo({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
+ message: `The push EVSEs process for endpoint ${oicpEndpoint.name} is being processed`
+ });
+ // Build OICP Client
+ const oicpClient = await OICPClientFactory.getCpoOicpClient(tenant, oicpEndpoint);
+ // Send EVSEs
+ const sendEVSEDataResut = await oicpClient.sendEVSEs(!Utils.isUndefined(config.processAllEVSEs) ? config.processAllEVSEs : false);
+ Logging.logInfo({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_DATA,
+ message: `The push EVSEs process for endpoint ${oicpEndpoint.name} is completed (Success: ${sendEVSEDataResut.success}/Failure: ${sendEVSEDataResut.failure})`
+ });
+ } catch (error) {
+ // Log error
+ Logging.logActionExceptionMessage(tenant.id, ServerAction.OICP_PUSH_EVSE_DATA, error);
+ } finally {
+ // Release the lock
+ await LockingManager.release(oicpLock);
+ }
+ } else {
+ console.log('Could not get lock for OICPPushEvseDataTask');
+ }
+ }
+}
+

ADD
src/scheduler/tasks/oicp/OICPPushEvseStatusTask.ts
+import { OICPPushEvseStatusTaskConfig, TaskConfig } from '../../../types/TaskConfig';
+
+import Constants from '../../../utils/Constants';
+import LockingHelper from '../../../locking/LockingHelper';
+import LockingManager from '../../../locking/LockingManager';
+import Logging from '../../../utils/Logging';
+import OICPClientFactory from '../../../client/oicp/OICPClientFactory';
+import OICPEndpoint from '../../../types/oicp/OICPEndpoint';
+import OICPEndpointStorage from '../../../storage/mongodb/OICPEndpointStorage';
+import { OICPRegistrationStatus } from '../../../types/oicp/OICPRegistrationStatus';
+import { OICPRole } from '../../../types/oicp/OICPRole';
+import SchedulerTask from '../../SchedulerTask';
+import { ServerAction } from '../../../types/Server';
+import Tenant from '../../../types/Tenant';
+import TenantComponents from '../../../types/TenantComponents';
+import Utils from '../../../utils/Utils';
+
+const MODULE_NAME = 'OICPPushEvseStatusTask';
+
+export default class OICPPushEvseStatusTask extends SchedulerTask {
+
+ async processTenant(tenant: Tenant, config: TaskConfig): Promise<void> {
+ try {
+ // Check if OICP component is active
+ if (Utils.isTenantComponentActive(tenant, TenantComponents.OICP)) {
+ // Get all available endpoints
+ const oicpEndpoints = await OICPEndpointStorage.getOicpEndpoints(tenant.id, { role: OICPRole.CPO }, Constants.DB_PARAMS_MAX_LIMIT);
+ for (const oicpEndpoint of oicpEndpoints.result) {
+ await this.processOICPEndpoint(tenant, oicpEndpoint, config);
+ }
+ }
+ } catch (error) {
+ // Log error
+ Logging.logActionExceptionMessage(tenant.id, ServerAction.OICP_PUSH_EVSE_STATUSES, error);
+ }
+ }
+
+ private async processOICPEndpoint(tenant: Tenant, oicpEndpoint: OICPEndpoint, config: OICPPushEvseStatusTaskConfig): Promise<void> {
+ // Get the lock
+ const oicpLock = await LockingHelper.createOICPPatchCpoEvseStatusesLock(tenant.id, oicpEndpoint);
+ if (oicpLock) {
+ try {
+ // Check if OICP endpoint is registered
+ if (oicpEndpoint.status !== OICPRegistrationStatus.REGISTERED) {
+ Logging.logDebug({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_STATUSES,
+ message: `The OICP Endpoint ${oicpEndpoint.name} is not registered. Skipping the oicpendpoint.`
+ });
+ return;
+ } else if (!oicpEndpoint.backgroundPatchJob) {
+ Logging.logDebug({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_STATUSES,
+ message: `The OICP Endpoint ${oicpEndpoint.name} is inactive.`
+ });
+ return;
+ }
+ Logging.logInfo({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_STATUSES,
+ message: `The push EVSE Statuses process for endpoint ${oicpEndpoint.name} is being processed`
+ });
+ // Build OICP Client
+ const oicpClient = await OICPClientFactory.getCpoOicpClient(tenant, oicpEndpoint);
+ // Send EVSE statuses
+ const sendEVSEStatusResult = await oicpClient.sendEVSEStatuses(!Utils.isUndefined(config.processAllEVSEs) ? config.processAllEVSEs : false);
+ Logging.logInfo({
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'processOICPEndpoint',
+ action: ServerAction.OICP_PUSH_EVSE_STATUSES,
+ message: `The push EVSE Statuses process for endpoint ${oicpEndpoint.name} is completed (Success: ${sendEVSEStatusResult.success}/Failure: ${sendEVSEStatusResult.failure})`
+ });
+ } catch (error) {
+ // Log error
+ Logging.logActionExceptionMessage(tenant.id, ServerAction.OICP_PUSH_EVSE_STATUSES, error);
+ } finally {
+ // Release the lock
+ await LockingManager.release(oicpLock);
+ }
+ } else {
+ console.log('Could not get lock for OICPPushEvseStatusTask');
+ }
+ }
+}
+

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -155,7 +155,7 @@ export enum ServerAction {
OICP_ENPOINT_PING = 'OicpEndpointPing',
OICP_ENPOINT_TRIGGER_JOBS = 'OicpEndpointTriggerJobs',
OICP_ENDPOINT = 'OicpEndpoint',
- OICP_PUSH_EVSES = 'OicpPushEvses',
+ OICP_PUSH_EVSE_DATA = 'OicpPushEvseData',
OICP_PUSH_EVSE_STATUSES = 'OicpPushEvseStatuses',
OICP_UPDATE_EVSE_STATUS = 'OicpUpdateEvseStatus',
OICP_AUTHORIZE_START = 'OicpAuthorizeStart',

MODIFY
src/types/TaskConfig.ts
src/types/TaskConfig.ts
@@ -36,3 +36,11 @@ export interface OCPIGetLocationsTaskConfig extends TaskConfig {
export interface OCPIPushLocationsTaskConfig extends TaskConfig {
processAllEVSEs?: boolean;
}
+
+export interface OICPPushEvseDataTaskConfig extends TaskConfig {
+ processAllEVSEs?: boolean;
+}
+
+export interface OICPPushEvseStatusTaskConfig extends TaskConfig {
+ processAllEVSEs?: boolean;
+}

Push OICP EVSE (Status) Task