@citrineos/data
Version:
The OCPP data module which includes all persistence layer implementation.
411 lines • 19.2 kB
JavaScript
import { CrudRepository, OCPP2_0_1 } from '@citrineos/base';
import { SequelizeRepository } from './Base.js';
import {} from '../../../interfaces/index.js';
import { Op } from 'sequelize';
import { Component, EvseType, Variable, VariableAttribute, VariableCharacteristics, VariableStatus, } from '../model/index.js';
import { ComponentVariable } from '../model/DeviceModel/ComponentVariable.js';
import { Sequelize } from 'sequelize-typescript';
import { Logger } from 'tslog';
// TODO: Document this
export class SequelizeDeviceModelRepository extends SequelizeRepository {
variable;
component;
evse;
variableCharacteristics;
componentVariable;
variableStatus;
constructor(config, logger, sequelizeInstance, variable, component, evse, componentVariable, variableCharacteristics, variableStatus) {
super(config, VariableAttribute.MODEL_NAME, logger, sequelizeInstance);
this.variable = variable
? variable
: new SequelizeRepository(config, Variable.MODEL_NAME, logger, sequelizeInstance);
this.component = component
? component
: new SequelizeRepository(config, Component.MODEL_NAME, logger, sequelizeInstance);
this.evse = evse
? evse
: new SequelizeRepository(config, EvseType.MODEL_NAME, logger, sequelizeInstance);
this.componentVariable = componentVariable
? componentVariable
: new SequelizeRepository(config, ComponentVariable.MODEL_NAME, logger, sequelizeInstance);
this.variableCharacteristics = variableCharacteristics
? variableCharacteristics
: new SequelizeRepository(config, VariableCharacteristics.MODEL_NAME, logger, sequelizeInstance);
this.variableStatus = variableStatus
? variableStatus
: new SequelizeRepository(config, VariableStatus.MODEL_NAME, logger, sequelizeInstance);
}
async createOrUpdateDeviceModelByStationId(tenantId, value, stationId, isoTimestamp) {
// Doing this here so that no records are created if the data is invalid
const variableAttributeTypes = value.variableAttribute.map((attr) => attr.type ?? OCPP2_0_1.AttributeEnumType.Actual);
if (variableAttributeTypes.length !== new Set(variableAttributeTypes).size) {
throw new Error('All variable attributes in ReportData must have different types.');
}
const [component, variable] = await this.findOrCreateEvseAndComponentAndVariable(tenantId, value.component, value.variable, stationId);
let dataType = null;
if (value.variableCharacteristics) {
const variableCharacteristicsType = value.variableCharacteristics;
dataType = variableCharacteristicsType.dataType;
const vc = {
tenantId,
unit: variableCharacteristicsType.unit ?? null,
dataType,
minLimit: variableCharacteristicsType.minLimit ?? null,
maxLimit: variableCharacteristicsType.maxLimit ?? null,
valuesList: variableCharacteristicsType.valuesList ?? null,
supportsMonitoring: variableCharacteristicsType.supportsMonitoring,
variableId: variable.id,
};
await this.s.transaction(async (transaction) => {
const savedVariableCharacteristics = await this.s.models[VariableCharacteristics.MODEL_NAME].findOne({
where: {
variableId: variable.id,
},
transaction,
});
if (!savedVariableCharacteristics) {
const createdVariableCharacteristics = await VariableCharacteristics.create(vc, {
transaction,
});
this.variableCharacteristics.emit('created', [createdVariableCharacteristics]);
return createdVariableCharacteristics;
}
else {
return await this.variableCharacteristics.updateAllByQuery(tenantId, vc, {
where: {
variableId: variable.id,
},
transaction,
});
}
});
}
return await Promise.all(value.variableAttribute.map(async (variableAttribute) => {
const [savedVariableAttribute, variableAttributeCreated] = await this.readOrCreateByQuery(tenantId, {
where: {
tenantId,
// the composite unique index of VariableAttribute
stationId: stationId,
variableId: variable.id,
componentId: component.id,
type: variableAttribute.type ?? OCPP2_0_1.AttributeEnumType.Actual,
},
defaults: {
evseDatabaseId: component.evseDatabaseId,
dataType,
value: variableAttribute.value,
generatedAt: isoTimestamp,
mutability: variableAttribute.mutability ?? OCPP2_0_1.MutabilityEnumType.ReadWrite,
persistent: variableAttribute.persistent ? variableAttribute.persistent : false,
constant: variableAttribute.constant ? variableAttribute.constant : false,
},
});
if (!variableAttributeCreated) {
return (await this.updateByKey(tenantId, {
evseDatabaseId: component.evseDatabaseId,
dataType: dataType ?? savedVariableAttribute.dataType,
type: variableAttribute.type ?? savedVariableAttribute.type,
value: variableAttribute.value ?? null,
mutability: variableAttribute.mutability ?? savedVariableAttribute.mutability,
persistent: variableAttribute.persistent ?? false,
constant: variableAttribute.constant ?? false,
generatedAt: isoTimestamp,
}, savedVariableAttribute.id));
}
return savedVariableAttribute;
}));
}
async findOrCreateEvseAndComponentAndVariable(tenantId, componentType, variableType, stationId) {
const component = await this.findOrCreateEvseAndComponent(tenantId, componentType, stationId);
const [variable] = await this.variable.readOrCreateByQuery(tenantId, {
where: {
tenantId,
name: variableType.name,
instance: variableType.instance ? variableType.instance : null,
},
defaults: {
...variableType,
},
});
// This can happen asynchronously
await this.componentVariable.readOrCreateByQuery(tenantId, {
where: { tenantId, componentId: component.id, variableId: variable.id },
});
return [component, variable];
}
async findOrCreateEvseAndComponent(tenantId, componentType, stationId) {
const evse = componentType.evse
? (await this.evse.readOrCreateByQuery(tenantId, {
where: {
tenantId,
id: componentType.evse.id,
connectorId: componentType.evse.connectorId ? componentType.evse.connectorId : null,
},
}))[0]
: undefined;
const [component, componentCreated] = await this.component.readOrCreateByQuery(tenantId, {
where: {
tenantId,
name: componentType.name,
instance: componentType.instance ? componentType.instance : null,
},
});
// Note: this permits changing the evse related to the component
if (component.evseDatabaseId !== evse?.databaseId && evse) {
await this.component.updateByKey(tenantId, { evseDatabaseId: evse.databaseId }, component.get('id'));
}
if (componentCreated && stationId) {
const defaultComponentVariableNames = ['Present', 'Available', 'Enabled'];
for (const defaultComponentVariableName of defaultComponentVariableNames) {
const [defaultComponentVariable, _defaultComponentVariableCreated] = await this.variable.readOrCreateByQuery(tenantId, {
where: {
tenantId,
name: defaultComponentVariableName,
instance: null,
},
});
await this.componentVariable.readOrCreateByQuery(tenantId, {
where: { tenantId, componentId: component.id, variableId: defaultComponentVariable.id },
});
await this.create(tenantId, VariableAttribute.build({
tenantId,
stationId,
variableId: defaultComponentVariable.id,
componentId: component.id,
evseDatabaseId: evse?.databaseId,
dataType: OCPP2_0_1.DataEnumType.boolean,
value: 'true',
mutability: OCPP2_0_1.MutabilityEnumType.ReadOnly,
}));
}
}
return component;
}
async createOrUpdateByGetVariablesResultAndStationId(tenantId, getVariablesResult, stationId, isoTimestamp) {
const savedVariableAttributes = [];
for (const result of getVariablesResult) {
const savedVariableAttribute = (await this.createOrUpdateDeviceModelByStationId(tenantId, {
component: {
...result.component,
},
variable: {
...result.variable,
},
variableAttribute: [
{
type: result.attributeType,
value: result.attributeValue,
},
],
}, stationId, isoTimestamp))[0];
await this.variableStatus.create(tenantId, VariableStatus.build({
tenantId,
value: result.attributeValue,
status: result.attributeStatus,
statusInfo: result.attributeStatusInfo,
variableAttributeId: savedVariableAttribute.get('id'),
}, { include: [VariableAttribute] }));
savedVariableAttributes.push(savedVariableAttribute);
}
return savedVariableAttributes;
}
async createOrUpdateBySetVariablesDataAndStationId(tenantId, setVariablesData, stationId, isoTimestamp) {
const savedVariableAttributes = [];
for (const data of setVariablesData) {
const savedVariableAttribute = (await this.createOrUpdateDeviceModelByStationId(tenantId, {
component: {
...data.component,
},
variable: {
...data.variable,
},
variableAttribute: [
{
type: data.attributeType,
value: data.attributeValue,
},
],
}, stationId, isoTimestamp))[0];
savedVariableAttributes.push(savedVariableAttribute);
}
return savedVariableAttributes;
}
async updateResultByStationId(tenantId, result, stationId, isoTimestamp) {
const savedVariableAttribute = await super.readOnlyOneByQuery(tenantId, {
where: { stationId, type: result.attributeType ?? OCPP2_0_1.AttributeEnumType.Actual },
include: [
{
model: Component,
where: {
name: result.component.name,
instance: result.component.instance ? result.component.instance : null,
},
},
{
model: Variable,
where: {
name: result.variable.name,
instance: result.variable.instance ? result.variable.instance : null,
},
},
],
});
if (savedVariableAttribute) {
await this.variableStatus.create(tenantId, VariableStatus.build({
tenantId,
value: savedVariableAttribute.value,
status: result.attributeStatus,
statusInfo: result.attributeStatusInfo,
variableAttributeId: savedVariableAttribute.get('id'),
}));
if (result.attributeStatus !== OCPP2_0_1.SetVariableStatusEnumType.Accepted) {
const mostRecentAcceptedStatus = (await this.variableStatus.readAllByQuery(tenantId, {
where: {
variableAttributeId: savedVariableAttribute.get('id'),
status: OCPP2_0_1.SetVariableStatusEnumType.Accepted,
},
limit: 1,
order: [['createdAt', 'DESC']],
}))[0];
savedVariableAttribute.setDataValue('value', mostRecentAcceptedStatus?.value);
}
savedVariableAttribute.set('generatedAt', isoTimestamp);
await savedVariableAttribute.save();
// Reload in order to include the statuses
return await savedVariableAttribute.reload({
include: [VariableStatus],
});
}
else {
throw new Error('Unable to update variable attribute status...');
}
}
async readAllSetVariableByStationId(tenantId, stationId) {
const variableAttributeArray = await super.readAllByQuery(tenantId, {
where: {
stationId,
bootConfigSetId: { [Op.ne]: null },
},
include: [{ model: Component, include: [EvseType] }, Variable],
});
return variableAttributeArray.map((variableAttribute) => this.createSetVariableDataType(variableAttribute));
}
async readAllByQuerystring(tenantId, query) {
const readQuery = this.constructQuery(query);
readQuery.include.push(VariableStatus);
return await super.readAllByQuery(tenantId, readQuery);
}
async existByQuerystring(tenantId, query) {
return await super.existByQuery(tenantId, this.constructQuery(query));
}
async deleteAllByQuerystring(tenantId, query) {
return await super.deleteAllByQuery(tenantId, this.constructQuery(query));
}
async findComponentAndVariable(tenantId, componentType, variableType) {
const component = await this.component.readOnlyOneByQuery(tenantId, {
where: {
name: componentType.name,
instance: componentType.instance ? componentType.instance : null,
},
});
const variable = await this.variable.readOnlyOneByQuery(tenantId, {
where: {
name: variableType.name,
instance: variableType.instance ? variableType.instance : null,
},
});
if (variable) {
const variableCharacteristics = await this.variableCharacteristics.readOnlyOneByQuery(tenantId, {
where: { variableId: variable.get('id') },
});
variable.variableCharacteristics = variableCharacteristics;
}
return [component, variable];
}
async findEvseByIdAndConnectorId(tenantId, id, connectorId) {
const storedEvses = await this.evse.readAllByQuery(tenantId, {
where: {
id: id,
connectorId: connectorId,
},
});
return storedEvses.length > 0 ? storedEvses[0] : undefined;
}
async findVariableCharacteristicsByVariableNameAndVariableInstance(tenantId, variableName, variableInstance) {
const variableCharacteristics = await this.variableCharacteristics.readAllByQuery(tenantId, {
include: [
{
model: Variable,
where: {
name: variableName,
instance: variableInstance,
},
},
],
});
return variableCharacteristics.length > 0 ? variableCharacteristics[0] : undefined;
}
/**
* Private Methods
*/
createSetVariableDataType(input) {
if (!input.value) {
throw new Error('Value must be present to generate SetVariableDataType from VariableAttribute');
}
else {
return {
attributeType: input.type,
attributeValue: input.value,
component: {
...input.component,
},
variable: {
...input.variable,
},
};
}
}
constructQuery(queryParams) {
const evseInclude = queryParams.component_evse_id ?? queryParams.component_evse_connectorId
? {
model: EvseType,
where: {
...(queryParams.component_evse_id ? { id: queryParams.component_evse_id } : {}),
...(queryParams.component_evse_connectorId
? { connectorId: queryParams.component_evse_connectorId }
: {}),
},
}
: EvseType;
const attributeType = queryParams.type && queryParams.type.toUpperCase() === 'NULL' ? null : queryParams.type;
return {
where: {
...(queryParams.stationId ? { stationId: queryParams.stationId } : {}),
...(queryParams.type === undefined ? {} : { type: attributeType }),
...(queryParams.value ? { value: queryParams.value } : {}),
// TODO: Currently, the status param doesn't work since status of VariableAttribute are stored in
// VariableStatuses table separately. The table stores status history. We need find a proper way to filter it.
...(queryParams.status === undefined ? {} : { status: queryParams.status }),
},
include: [
{
model: Component,
where: {
...(queryParams.component_name ? { name: queryParams.component_name } : {}),
...(queryParams.component_instance ? { instance: queryParams.component_instance } : {}),
},
include: [evseInclude],
},
{
model: Variable,
where: {
...(queryParams.variable_name ? { name: queryParams.variable_name } : {}),
...(queryParams.variable_instance ? { instance: queryParams.variable_instance } : {}),
},
include: [VariableCharacteristics],
},
],
};
}
}
//# sourceMappingURL=DeviceModel.js.map