'use strict'; const logger = require('../logger'); const nantum = require('../modules/nantum'); const { v4 } = require('uuid'); const reportSubscriptionParameters = { dataGranularitySeconds: 60, reportBackSeconds: 60, subscriptionDurationSeconds: 60 * 60, resubscribeDurationSeconds: 59 * 60, resubscribeAfterNoDataForSeconds: 90 }; function getSecondsSince(property, reportMetadata) { const value = reportMetadata[property]; if (value != null) { const millisDiff = new Date().getTime() - new Date(value).getTime(); if (millisDiff > 0) { return Math.round(millisDiff / 1000); } } } async function pollForReports( oadrPoll, clientCertificateCn, clientCertificateFingerprint, ) { logger.info( 'pollForReports', oadrPoll, clientCertificateCn, clientCertificateFingerprint, ); const report = await nantum.fetchReport(clientCertificateFingerprint); const createRequests = []; if (report.venReportMetadata) { for (const reportMetadata of report.venReportMetadata) { let sendCreate = false; if (!reportMetadata.lastSentCreate) { // if we've never sent a subscription request, do it logger.info('sending create because we never have', reportMetadata.reportSpecifierId); sendCreate = true; } else { // have sent a create > 5s ago, not received a created if ( !reportMetadata.lastReceivedCreated && getSecondsSince('lastSentCreate', reportMetadata) > 5 ) { logger.info('no reply to creation request, send another', reportMetadata.reportSpecifierId); sendCreate = true; } } if ( getSecondsSince('lastReceivedUpdate', reportMetadata) > reportSubscriptionParameters.resubscribeAfterNoDataForSeconds ) { // previously received data, silent now sendCreate = true; } if ( !reportMetadata.lastReceivedUpdate && getSecondsSince('lastReceivedCreated', reportMetadata) > reportSubscriptionParameters.reportBackSeconds + 5 ) { // if we haven't received any data but we've waited long enough for one data interval + 5 seconds logger.info('sending create because have not received data', reportMetadata.reportSpecifierId); sendCreate = true; } if ( getSecondsSince('lastReceivedCreated', reportMetadata) > reportSubscriptionParameters.resubscribeDurationSeconds ) { // when we're close to the end of the subscription, trigger a resubscribe logger.info('sending create because close to end of subscription', reportMetadata.reportSpecifierId); sendCreate = true; } if (sendCreate) { const newReportRequestId = v4(); // track the last 10 registration ids reportMetadata.reportRequestIds = [ newReportRequestId, ...reportMetadata.reportRequestIds, ].slice(0, 10); createRequests.push({ reportRequestId: newReportRequestId, reportSpecifierId: reportMetadata.reportSpecifierId, granularityDuration: `PT${reportSubscriptionParameters.dataGranularitySeconds}S`, reportBackDuration: `PT${reportSubscriptionParameters.reportBackSeconds}S`, startDate: new Date().toISOString(), duration: `PT${reportSubscriptionParameters.subscriptionDurationSeconds}S`, specifiers: reportMetadata.descriptions.map(description => ({ reportId: description.reportId, readingType: 'x-notApplicable', })), }); reportMetadata.lastSentCreate = new Date().toISOString(); } } if (createRequests.length > 0) { const createReport = { _type: 'oadrCreateReport', requestId: v4(), requests: createRequests, }; await nantum.updateReport(clientCertificateFingerprint, report); return createReport; } } } async function registerReports( oadrRegisterReport, clientCertificateCn, clientCertificateFingerprint, ) { logger.info( 'registerReports', oadrRegisterReport, clientCertificateCn, clientCertificateFingerprint, ); const requestVenId = oadrRegisterReport.venId; validateVenId(requestVenId, clientCertificateFingerprint, false); const venReportMetadata = (oadrRegisterReport.reports || []).map(report => { const { reportSpecifierId, descriptions } = report; const lastReceivedRegister = new Date().toISOString(); const reportRequestId = v4(); return { reportRequestIds: [reportRequestId], reportSpecifierId, descriptions, lastReceivedRegister, }; }); //TODO: whitelist based off Nantum API sensors await nantum.updateReport(clientCertificateFingerprint, { venReportMetadata, }); return { _type: 'oadrRegisteredReport', responseCode: '200', responseRequestId: oadrRegisterReport.requestId, responseDescription: 'OK', requests: [], }; } async function createdReports( oadrCreatedReport, clientCertificateCn, clientCertificateFingerprint, ) { logger.info( 'createdReports', oadrCreatedReport, clientCertificateCn, clientCertificateFingerprint, ); validateVenId(oadrCreatedReport.venId, clientCertificateFingerprint, false); if (oadrCreatedReport.pendingReports) { // flag reports as having been created const report = await nantum.fetchReport(clientCertificateFingerprint); if (report.venReportMetadata) { for (const pendingReport of oadrCreatedReport.pendingReports) { const reportRequestId = pendingReport['reportRequestId']; const match = report.venReportMetadata.filter(x => x.reportRequestIds.includes(reportRequestId), )[0]; if (match) { match.lastReceivedCreated = new Date().toISOString(); } else { logger.info( 'could not match', reportRequestId, report.venReportMetadata, ); } } } await nantum.updateReport(clientCertificateFingerprint, report); } return { _type: 'oadrResponse', responseCode: '200', responseDescription: 'OK', venId: clientCertificateFingerprint, }; } async function receiveReportData( oadrUpdateReport, clientCertificateCn, clientCertificateFingerprint, ) { logger.info( 'receiveReportData', oadrUpdateReport, clientCertificateCn, clientCertificateFingerprint, ); const requestVenId = oadrUpdateReport.venId; validateVenId(requestVenId, clientCertificateFingerprint, false); const report = await nantum.fetchReport(clientCertificateFingerprint); if (report.venReportMetadata) { for (const updateReport of oadrUpdateReport.reports) { const reportRequestId = updateReport.reportRequestId; const match = report.venReportMetadata.filter(x => x.reportRequestIds.includes(reportRequestId), )[0]; if (!match) { logger.info( 'could not match', reportRequestId, report.venReportMetadata, ); continue; } match.lastReceivedUpdate = new Date().toISOString(); for (const interval of updateReport.intervals || []) { const reportId = interval.reportPayloads[0].reportId; const date = interval.startDate; if (interval.reportPayloads[0].payloadFloat) { const value = interval.reportPayloads[0].payloadFloat; logger.info('received report', [ date, clientCertificateFingerprint, updateReport.reportSpecifierId, updateReport.reportName, reportRequestId, reportId, value, ]); } if ( interval.reportPayloads[0].payloadStatus && interval.reportPayloads[0].payloadStatus.loadControlState ) { const loadControlState = interval.reportPayloads[0].payloadStatus.loadControlState; Object.keys(loadControlState).forEach(type => { const typeObj = loadControlState[type]; Object.keys(typeObj).forEach(subType => { const value = typeObj[subType]; logger.info('received report', [ date, clientCertificateFingerprint, updateReport.reportSpecifierId, updateReport.reportName, reportRequestId, reportId, type, subType, value, ]); }); }); } } } } await nantum.updateReport(clientCertificateFingerprint, report); return { _type: 'oadrUpdatedReport', responseCode: '200', responseRequestId: oadrUpdateReport.requestId, responseDescription: 'OK', venId: clientCertificateFingerprint, }; } function validateVenId(requestVenId, clientCertificateFingerprint, required) { if (requestVenId === clientCertificateFingerprint) { return; } if (!required && requestVenId == null) { return; } if (required && requestVenId == null) { const error = new Error('VenID is missing'); error.responseCode = 452; throw error; } const error = new Error('VenID does not match certificate'); error.responseCode = 452; throw error; } module.exports = { registerReports, createdReports, pollForReports, receiveReportData, };