| amperageLimitationUnit | A/cA/dA/mA | A | string | charging stations amperage limit unit |
| enableStatistics | true/false | true | boolean | enable charging stations statistics |
| mayAuthorizeAtRemoteStart | true/false | true | boolean | always send authorize at remote start transaction when AuthorizeRemoteTxRequests is enabled |
+| payloadSchemaValidation | true/false | true | boolean | validate OCPP commands PDU against OCA JSON schemas |
| beginEndMeterValues | true/false | false | boolean | enable Transaction.{Begin,End} MeterValues |
| outOfOrderEndMeterValues | true/false | false | boolean | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter) |
| meteringPerTransaction | true/false | true | boolean | enable metering history on a per transaction basis |
"@mikro-orm/mariadb": "^5.3.1",
"@mikro-orm/reflection": "^5.3.1",
"@mikro-orm/sqlite": "^5.3.1",
+ "ajv": "^8.11.0",
+ "ajv-draft-04": "^1.0.0",
+ "ajv-formats": "^2.1.1",
"basic-ftp": "^5.0.1",
"chalk": "^4.1.2",
"express": "^4.18.1",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
- "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ajv-draft-04": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
+ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+ "peerDependencies": {
+ "ajv": "^8.5.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "dev": true,
"dependencies": {
"ajv": "^8.0.0"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-diff": {
"version": "1.2.0",
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json-schema-typed": {
"version": "7.0.3",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true,
"engines": {
"node": ">=6"
}
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
- "dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"uri-js": "^4.2.2"
}
},
+ "ajv-draft-04": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
+ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+ "requires": {}
+ },
"ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "dev": true,
"requires": {
"ajv": "^8.0.0"
}
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-diff": {
"version": "1.2.0",
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"json-schema-typed": {
"version": "7.0.3",
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"require-main-filename": {
"version": "2.0.0",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"requires": {
"punycode": "^2.1.0"
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
"@mikro-orm/mariadb": "^5.3.1",
"@mikro-orm/reflection": "^5.3.1",
"@mikro-orm/sqlite": "^5.3.1",
+ "ajv": "^8.11.0",
+ "ajv-draft-04": "^1.0.0",
+ "ajv-formats": "^2.1.1",
"basic-ftp": "^5.0.1",
"chalk": "^4.1.2",
"express": "^4.18.1",
},
],
external: [
+ '@mikro-orm/core',
+ '@mikro-orm/reflection',
+ 'ajv',
+ 'ajv-draft-04',
+ 'ajv-formats',
'basic-ftp',
'chalk',
'crypto',
'express',
'fs',
- '@mikro-orm/core',
- '@mikro-orm/reflection',
'mnemonist/lru-map-with-delete',
'moment',
'mongodb',
'tar',
'url',
'uuid',
- 'ws',
+ 'winston',
'winston-daily-rotate-file',
'winston/lib/winston/transports/index.js',
- 'winston',
'worker_threads',
+ 'ws',
],
plugins: [
json(),
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:ChangeAvailabilityRequest",
+ "title": "ChangeAvailabilityRequest",
+ "type": "object",
+ "properties": {
+ "connectorId": {
+ "type": "integer"
+ },
+ "type": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["Inoperative", "Operative"]
+ }
+ },
+ "additionalProperties": false,
+ "required": ["connectorId", "type"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:ChangeConfigurationRequest",
+ "title": "ChangeConfigurationRequest",
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string",
+ "maxLength": 50
+ },
+ "value": {
+ "type": "string",
+ "maxLength": 500
+ }
+ },
+ "additionalProperties": false,
+ "required": ["key", "value"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:ClearCacheRequest",
+ "title": "ClearCacheRequest",
+ "type": "object",
+ "properties": {},
+ "additionalProperties": false
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:ClearChargingProfileRequest",
+ "title": "ClearChargingProfileRequest",
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "connectorId": {
+ "type": "integer"
+ },
+ "chargingProfilePurpose": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["ChargePointMaxProfile", "TxDefaultProfile", "TxProfile"]
+ },
+ "stackLevel": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:GetConfigurationRequest",
+ "title": "GetConfigurationRequest",
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 50
+ }
+ }
+ },
+ "additionalProperties": false
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:GetDiagnosticsRequest",
+ "title": "GetDiagnosticsRequest",
+ "type": "object",
+ "properties": {
+ "location": {
+ "type": "string",
+ "format": "uri"
+ },
+ "retries": {
+ "type": "integer"
+ },
+ "retryInterval": {
+ "type": "integer"
+ },
+ "startTime": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "stopTime": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["location"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:RemoteStartTransactionRequest",
+ "title": "RemoteStartTransactionRequest",
+ "type": "object",
+ "properties": {
+ "connectorId": {
+ "type": "integer"
+ },
+ "idTag": {
+ "type": "string",
+ "maxLength": 20
+ },
+ "chargingProfile": {
+ "type": "object",
+ "properties": {
+ "chargingProfileId": {
+ "type": "integer"
+ },
+ "transactionId": {
+ "type": "integer"
+ },
+ "stackLevel": {
+ "type": "integer"
+ },
+ "chargingProfilePurpose": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["ChargePointMaxProfile", "TxDefaultProfile", "TxProfile"]
+ },
+ "chargingProfileKind": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["Absolute", "Recurring", "Relative"]
+ },
+ "recurrencyKind": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["Daily", "Weekly"]
+ },
+ "validFrom": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "validTo": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "chargingSchedule": {
+ "type": "object",
+ "properties": {
+ "duration": {
+ "type": "integer"
+ },
+ "startSchedule": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "chargingRateUnit": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["A", "W"]
+ },
+ "chargingSchedulePeriod": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "startPeriod": {
+ "type": "integer"
+ },
+ "limit": {
+ "type": "number",
+ "multipleOf": 0.1
+ },
+ "numberPhases": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["startPeriod", "limit"]
+ }
+ },
+ "minChargingRate": {
+ "type": "number",
+ "multipleOf": 0.1
+ }
+ },
+ "additionalProperties": false,
+ "required": ["chargingRateUnit", "chargingSchedulePeriod"]
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "chargingProfileId",
+ "stackLevel",
+ "chargingProfilePurpose",
+ "chargingProfileKind",
+ "chargingSchedule"
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "required": ["idTag"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:RemoteStopTransactionRequest",
+ "title": "RemoteStopTransactionRequest",
+ "type": "object",
+ "properties": {
+ "transactionId": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["transactionId"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:ResetRequest",
+ "title": "ResetRequest",
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["Hard", "Soft"]
+ }
+ },
+ "additionalProperties": false,
+ "required": ["type"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:SetChargingProfileRequest",
+ "title": "SetChargingProfileRequest",
+ "type": "object",
+ "properties": {
+ "connectorId": {
+ "type": "integer"
+ },
+ "csChargingProfiles": {
+ "type": "object",
+ "properties": {
+ "chargingProfileId": {
+ "type": "integer"
+ },
+ "transactionId": {
+ "type": "integer"
+ },
+ "stackLevel": {
+ "type": "integer"
+ },
+ "chargingProfilePurpose": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["ChargePointMaxProfile", "TxDefaultProfile", "TxProfile"]
+ },
+ "chargingProfileKind": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["Absolute", "Recurring", "Relative"]
+ },
+ "recurrencyKind": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["Daily", "Weekly"]
+ },
+ "validFrom": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "validTo": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "chargingSchedule": {
+ "type": "object",
+ "properties": {
+ "duration": {
+ "type": "integer"
+ },
+ "startSchedule": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "chargingRateUnit": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": ["A", "W"]
+ },
+ "chargingSchedulePeriod": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "startPeriod": {
+ "type": "integer"
+ },
+ "limit": {
+ "type": "number",
+ "multipleOf": 0.1
+ },
+ "numberPhases": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["startPeriod", "limit"]
+ }
+ },
+ "minChargingRate": {
+ "type": "number",
+ "multipleOf": 0.1
+ }
+ },
+ "additionalProperties": false,
+ "required": ["chargingRateUnit", "chargingSchedulePeriod"]
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "chargingProfileId",
+ "stackLevel",
+ "chargingProfilePurpose",
+ "chargingProfileKind",
+ "chargingSchedule"
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "required": ["connectorId", "csChargingProfiles"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:TriggerMessageRequest",
+ "title": "TriggerMessageRequest",
+ "type": "object",
+ "properties": {
+ "requestedMessage": {
+ "type": "string",
+ "additionalProperties": false,
+ "enum": [
+ "BootNotification",
+ "DiagnosticsStatusNotification",
+ "FirmwareStatusNotification",
+ "Heartbeat",
+ "MeterValues",
+ "StatusNotification"
+ ]
+ },
+ "connectorId": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["requestedMessage"]
+}
--- /dev/null
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "urn:OCPP:1.6:2019:12:UnlockConnectorRequest",
+ "title": "UnlockConnectorRequest",
+ "type": "object",
+ "properties": {
+ "connectorId": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["connectorId"]
+}
return this.stationInfo.mayAuthorizeAtRemoteStart ?? true;
}
+ public getPayloadSchemaValidation(): boolean | undefined {
+ return this.stationInfo.payloadSchemaValidation ?? true;
+ }
+
public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined {
const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
switch (this.getCurrentOutType(stationInfo)) {
import path from 'path';
import { URL, fileURLToPath } from 'url';
+import { JSONSchemaType } from 'ajv';
import { Client, FTPResponse } from 'basic-ftp';
import tar from 'tar';
MessageTrigger,
OCPP16AvailabilityType,
OCPP16BootNotificationRequest,
+ OCPP16ClearCacheRequest,
OCPP16HeartbeatRequest,
OCPP16IncomingRequestCommand,
OCPP16RequestCommand,
export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
+ private resetJsonSchema: JSONSchemaType<ResetRequest>;
+ private clearCacheJsonSchema: JSONSchemaType<OCPP16ClearCacheRequest>;
+ private getConfigurationJsonSchema: JSONSchemaType<GetConfigurationRequest>;
+ private changeConfigurationJsonSchema: JSONSchemaType<ChangeConfigurationRequest>;
+ private unlockConnectorJsonSchema: JSONSchemaType<UnlockConnectorRequest>;
+ private getDiagnosticsJsonSchema: JSONSchemaType<GetDiagnosticsRequest>;
+ private setChargingProfileJsonSchema: JSONSchemaType<SetChargingProfileRequest>;
+ private clearChargingProfileJsonSchema: JSONSchemaType<ClearChargingProfileRequest>;
+ private changeAvailabilityJsonSchema: JSONSchemaType<ChangeAvailabilityRequest>;
+ private remoteStartTransactionJsonSchema: JSONSchemaType<RemoteStartTransactionRequest>;
+ private remoteStopTransactionJsonSchema: JSONSchemaType<RemoteStopTransactionRequest>;
+ private triggerMessageJsonSchema: JSONSchemaType<OCPP16TriggerMessageRequest>;
public constructor() {
if (new.target?.name === moduleName) {
[OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
[OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
]);
+ this.resetJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/Reset.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ResetRequest>;
+ this.clearCacheJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16ClearCacheRequest>;
+ this.getConfigurationJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<GetConfigurationRequest>;
+ this.changeConfigurationJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ChangeConfigurationRequest>;
+ this.unlockConnectorJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<UnlockConnectorRequest>;
+ this.getDiagnosticsJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<GetDiagnosticsRequest>;
+ this.setChargingProfileJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<SetChargingProfileRequest>;
+ this.clearChargingProfileJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ClearChargingProfileRequest>;
+ this.changeAvailabilityJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<ChangeAvailabilityRequest>;
+ this.remoteStartTransactionJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<RemoteStartTransactionRequest>;
+ this.remoteStopTransactionJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<RemoteStopTransactionRequest>;
+ this.triggerMessageJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16TriggerMessageRequest>;
}
public async incomingRequestHandler(
) {
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `${commandName} cannot be issued to handle request payload ${JSON.stringify(
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
commandPayload,
null,
2
// Throw exception
throw new OCPPError(
ErrorType.NOT_IMPLEMENTED,
- `${commandName} is not implemented to handle request payload ${JSON.stringify(
+ `${commandName} is not implemented to handle request PDU ${JSON.stringify(
commandPayload,
null,
2
} else {
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `${commandName} cannot be issued to handle request payload ${JSON.stringify(
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
commandPayload,
null,
2
chargingStation: ChargingStation,
commandPayload: ResetRequest
): DefaultResponse {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.RESET,
+ this.resetJsonSchema,
+ commandPayload
+ );
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setImmediate(async (): Promise<void> => {
await chargingStation.reset((commandPayload.type + 'Reset') as OCPP16StopTransactionReason);
return Constants.OCPP_RESPONSE_ACCEPTED;
}
- private handleRequestClearCache(): DefaultResponse {
+ private handleRequestClearCache(
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16ClearCacheRequest
+ ): DefaultResponse {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.CLEAR_CACHE,
+ this.clearCacheJsonSchema,
+ commandPayload
+ );
return Constants.OCPP_RESPONSE_ACCEPTED;
}
chargingStation: ChargingStation,
commandPayload: UnlockConnectorRequest
): Promise<UnlockConnectorResponse> {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+ this.unlockConnectorJsonSchema,
+ commandPayload
+ );
const connectorId = commandPayload.connectorId;
if (connectorId === 0) {
logger.error(
chargingStation: ChargingStation,
commandPayload: GetConfigurationRequest
): GetConfigurationResponse {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+ this.getConfigurationJsonSchema,
+ commandPayload
+ );
const configurationKey: OCPPConfigurationKey[] = [];
const unknownKey: string[] = [];
if (Utils.isEmptyArray(commandPayload.key)) {
chargingStation: ChargingStation,
commandPayload: ChangeConfigurationRequest
): ChangeConfigurationResponse {
- // JSON request fields type sanity check
- if (!Utils.isString(commandPayload.key)) {
- logger.error(
- `${chargingStation.logPrefix()} ${
- OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
- } request key field is not a string:`,
- commandPayload
- );
- }
- if (!Utils.isString(commandPayload.value)) {
- logger.error(
- `${chargingStation.logPrefix()} ${
- OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
- } request value field is not a string:`,
- commandPayload
- );
- }
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+ this.changeConfigurationJsonSchema,
+ commandPayload
+ );
const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
chargingStation,
commandPayload.key,
chargingStation: ChargingStation,
commandPayload: SetChargingProfileRequest
): SetChargingProfileResponse {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+ this.setChargingProfileJsonSchema,
+ commandPayload
+ );
if (
!OCPP16ServiceUtils.checkFeatureProfile(
chargingStation,
chargingStation: ChargingStation,
commandPayload: ClearChargingProfileRequest
): ClearChargingProfileResponse {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+ this.clearChargingProfileJsonSchema,
+ commandPayload
+ );
if (
!OCPP16ServiceUtils.checkFeatureProfile(
chargingStation,
chargingStation: ChargingStation,
commandPayload: ChangeAvailabilityRequest
): Promise<ChangeAvailabilityResponse> {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+ this.changeAvailabilityJsonSchema,
+ commandPayload
+ );
const connectorId: number = commandPayload.connectorId;
if (!chargingStation.getConnectorStatus(connectorId)) {
logger.error(
chargingStation: ChargingStation,
commandPayload: RemoteStartTransactionRequest
): Promise<DefaultResponse> {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+ this.remoteStartTransactionJsonSchema,
+ commandPayload
+ );
const transactionConnectorId = commandPayload.connectorId;
const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
if (transactionConnectorId) {
chargingStation: ChargingStation,
commandPayload: RemoteStopTransactionRequest
): Promise<DefaultResponse> {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+ this.remoteStopTransactionJsonSchema,
+ commandPayload
+ );
const transactionId = commandPayload.transactionId;
for (const connectorId of chargingStation.connectors.keys()) {
if (
chargingStation: ChargingStation,
commandPayload: GetDiagnosticsRequest
): Promise<GetDiagnosticsResponse> {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+ this.getDiagnosticsJsonSchema,
+ commandPayload
+ );
if (
!OCPP16ServiceUtils.checkFeatureProfile(
chargingStation,
chargingStation: ChargingStation,
commandPayload: OCPP16TriggerMessageRequest
): OCPP16TriggerMessageResponse {
+ this.validateIncomingRequestPayload(
+ chargingStation,
+ OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+ this.triggerMessageJsonSchema,
+ commandPayload
+ );
if (
!OCPP16ServiceUtils.checkFeatureProfile(
chargingStation,
// Sanity check
if (!Array.isArray(commandParams?.meterValue)) {
throw new OCPPError(
- ErrorType.TYPERAINT_VIOLATION,
- `${moduleName}.buildRequestPayload ${commandName}: Invalid array type for meterValue payload field`,
+ ErrorType.TYPE_CONSTRAINT_VIOLATION,
+ `${moduleName}.buildRequestPayload ${commandName}: Invalid array type for meterValue PDU field`,
commandName,
commandParams
);
// Throw exception
throw new OCPPError(
ErrorType.NOT_IMPLEMENTED,
- `${commandName} is not implemented to handle request response payload ${JSON.stringify(
+ `${commandName} is not implemented to handle request response PDU ${JSON.stringify(
payload,
null,
2
} else {
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `${commandName} cannot be issued to handle request response payload ${JSON.stringify(
+ `${commandName} cannot be issued to handle request response PDU ${JSON.stringify(
payload,
null,
2
+import { JSONSchemaType } from 'ajv';
+import Ajv from 'ajv-draft-04';
+import ajvFormats from 'ajv-formats';
+
+import OCPPError from '../../exception/OCPPError';
import { HandleErrorParams } from '../../types/Error';
import { JsonType } from '../../types/JsonType';
+import { ErrorType } from '../../types/ocpp/ErrorType';
import { IncomingRequestCommand } from '../../types/ocpp/Requests';
import logger from '../../utils/Logger';
import type ChargingStation from '../ChargingStation';
+const moduleName = 'OCPPIncomingRequestService';
+
export default abstract class OCPPIncomingRequestService {
private static instance: OCPPIncomingRequestService | null = null;
+ private ajv: Ajv;
protected constructor() {
- // This is intentional
+ this.ajv = new Ajv();
+ ajvFormats(this.ajv);
}
public static getInstance<T extends OCPPIncomingRequestService>(this: new () => T): T {
params: HandleErrorParams<T> = { throwError: true }
): T {
logger.error(
- chargingStation.logPrefix() + ' Incoming request command %s error: %j',
+ `${chargingStation.logPrefix()} ${moduleName}.handleIncomingRequestError: Incoming request command %s error: %j`,
commandName,
error
);
}
}
+ protected validateIncomingRequestPayload<T extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: IncomingRequestCommand,
+ schema: JSONSchemaType<T>,
+ payload: T
+ ): boolean {
+ if (!chargingStation.getPayloadSchemaValidation()) {
+ return true;
+ }
+ const validate = this.ajv.compile(schema);
+ if (validate(payload)) {
+ return true;
+ }
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestPayload: Incoming request PDU is invalid: %j`,
+ validate.errors
+ );
+ throw new OCPPError(
+ ErrorType.FORMATION_VIOLATION,
+ 'Incoming request PDU is invalid',
+ commandName,
+ JSON.stringify(validate.errors, null, 2)
+ );
+ }
+
public abstract incomingRequestHandler(
chargingStation: ChargingStation,
messageId: string,
import type ChargingStation from '../ChargingStation';
import type OCPPResponseService from './OCPPResponseService';
+const moduleName = 'OCPPRequestService';
+
export default abstract class OCPPRequestService {
private static instance: OCPPRequestService | null = null;
}
throw new OCPPError(
ErrorType.SECURITY_ERROR,
- `Cannot send command ${commandName} payload when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
+ `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
commandName
);
}
import { RequestCommand } from '../../types/ocpp/Requests';
import type ChargingStation from '../ChargingStation';
+const moduleName = 'OCPPResponseService';
+
export default abstract class OCPPResponseService {
private static instance: OCPPResponseService | null = null;
reconnectExponentialDelay?: boolean;
registrationMaxRetries?: number;
enableStatistics?: boolean;
- mayAuthorizeAtRemoteStart: boolean;
+ mayAuthorizeAtRemoteStart?: boolean;
+ payloadSchemaValidation?: boolean;
amperageLimitationOcppKey?: string;
amperageLimitationUnit?: AmpereUnits;
beginEndMeterValues?: boolean;
DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
}
-export enum OCPP16IncomingRequestCommand {
- RESET = 'Reset',
- CLEAR_CACHE = 'ClearCache',
- CHANGE_AVAILABILITY = 'ChangeAvailability',
- UNLOCK_CONNECTOR = 'UnlockConnector',
- GET_CONFIGURATION = 'GetConfiguration',
- CHANGE_CONFIGURATION = 'ChangeConfiguration',
- SET_CHARGING_PROFILE = 'SetChargingProfile',
- CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
- REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
- REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
- GET_DIAGNOSTICS = 'GetDiagnostics',
- TRIGGER_MESSAGE = 'TriggerMessage',
-}
-
export type OCPP16HeartbeatRequest = EmptyObject;
export interface OCPP16BootNotificationRequest extends JsonObject {
vendorErrorCode?: string;
}
+export enum OCPP16IncomingRequestCommand {
+ RESET = 'Reset',
+ CLEAR_CACHE = 'ClearCache',
+ CHANGE_AVAILABILITY = 'ChangeAvailability',
+ UNLOCK_CONNECTOR = 'UnlockConnector',
+ GET_CONFIGURATION = 'GetConfiguration',
+ CHANGE_CONFIGURATION = 'ChangeConfiguration',
+ SET_CHARGING_PROFILE = 'SetChargingProfile',
+ CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
+ REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
+ REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
+ GET_DIAGNOSTICS = 'GetDiagnostics',
+ TRIGGER_MESSAGE = 'TriggerMessage',
+}
+
+export type OCPP16ClearCacheRequest = EmptyObject;
+
export interface ChangeConfigurationRequest extends JsonObject {
key: string | OCPP16StandardParametersKey;
value: string;
SECURITY_ERROR = 'SecurityError',
// Payload for Action is syntactically incorrect or not conform the PDU structure for Action
FORMATION_VIOLATION = 'FormationViolation',
+ FORMAT_VIOLATION = 'FormatViolation',
// Payload is syntactically correct but at least one field contains an invalid value
- PROPERTY_RAINT_VIOLATION = 'PropertyraintViolation',
- // Payload for Action is syntactically correct but at least one of the fields violates occurrence raints
- OCCURRENCE_RAINT_VIOLATION = 'OccurrenceraintViolation',
- // Payload for Action is syntactically correct but at least one of the fields violates data type raints (e.g. "somestring" = 12)
- TYPERAINT_VIOLATION = 'TyperaintViolation',
+ PROPERTY_CONSTRAINT_VIOLATION = 'PropertyConstraintViolation',
+ // Payload for Action is syntactically correct but at least one of the fields violates occurrence constraints
+ OCCURRENCE_CONSTRAINT_VIOLATION = 'OccurrenceConstraintViolation',
+ // Payload for Action is syntactically correct but at least one of the fields violates data type constraints (e.g. "somestring" = 12)
+ TYPE_CONSTRAINT_VIOLATION = 'TypeConstraintViolation',
// Any other error not covered by the previous ones
GENERIC_ERROR = 'GenericError',
}
} from './1.6/Requests';
import { MessageType } from './MessageType';
+export type RequestCommand = OCPP16RequestCommand;
+
+export const RequestCommand = {
+ ...OCPP16RequestCommand,
+};
+
export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType];
+export interface RequestParams {
+ skipBufferingOnError?: boolean;
+ triggerMessage?: boolean;
+}
+
+export type IncomingRequestCommand = OCPP16IncomingRequestCommand;
+
+export const IncomingRequestCommand = {
+ ...OCPP16IncomingRequestCommand,
+};
+
export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType];
export type CachedRequest = [
JsonType
];
-export type IncomingRequestHandler = (
- chargingStation: ChargingStation,
- commandPayload: JsonType
-) => JsonType | Promise<JsonType>;
-
-export type ResponseType = JsonType | OCPPError;
-
-export interface RequestParams {
- skipBufferingOnError?: boolean;
- triggerMessage?: boolean;
-}
-
export type BootNotificationRequest = OCPP16BootNotificationRequest;
export type HeartbeatRequest = OCPP16HeartbeatRequest;
export type MeterValuesRequest = OCPP16MeterValuesRequest;
+export type IncomingRequestHandler = (
+ chargingStation: ChargingStation,
+ commandPayload: JsonType
+) => JsonType | Promise<JsonType>;
+
export type AvailabilityType = OCPP16AvailabilityType;
export const AvailabilityType = {
...OCPP16AvailabilityType,
};
-export type RequestCommand = OCPP16RequestCommand;
-
-export const RequestCommand = {
- ...OCPP16RequestCommand,
-};
-
-export type IncomingRequestCommand = OCPP16IncomingRequestCommand;
-
-export const IncomingRequestCommand = {
- ...OCPP16IncomingRequestCommand,
-};
-
export type DiagnosticsStatus = OCPP16DiagnosticsStatus;
export const DiagnosticsStatus = {
...OCPP16DiagnosticsStatus,
};
+
+export type ResponseType = JsonType | OCPPError;