distribute-event.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. 'use strict';
  2. const _ = require('lodash');
  3. const {
  4. parseXML,
  5. childAttr,
  6. required,
  7. boolean,
  8. dateTime,
  9. duration,
  10. number,
  11. } = require('../parser');
  12. const {
  13. createDoc,
  14. parseEiResponse,
  15. parseEventSignalIntervals,
  16. serializeEventSignalIntervals,
  17. parsePayloadFloat,
  18. serializePayloadFloat,
  19. serializeEiResponse,
  20. serializeDuration,
  21. serializeDateTime,
  22. energyInteropPayloadsNs,
  23. energyInteropNs,
  24. oadrNs,
  25. powerNs,
  26. emixNs,
  27. calendarNs,
  28. calendarStreamNs,
  29. siScaleNs,
  30. xsiNs,
  31. } = require('../shared');
  32. const { fragment } = require('xmlbuilder2');
  33. const eiTargetMappings = [
  34. {
  35. xmlNs: 'power',
  36. xmlNsUri: powerNs,
  37. xmlElement: 'endDeviceAsset',
  38. xmlChildElement: 'mrid',
  39. json: 'end-device-asset',
  40. },
  41. {
  42. xmlNs: 'power',
  43. xmlNsUri: powerNs,
  44. xmlElement: 'aggregatedPnode',
  45. xmlChildElement: 'node',
  46. json: 'aggregated-pnode',
  47. },
  48. {
  49. xmlNs: 'power',
  50. xmlNsUri: powerNs,
  51. xmlElement: 'meterAsset',
  52. xmlChildElement: 'mrid',
  53. json: 'meter-asset',
  54. },
  55. {
  56. xmlNs: 'power',
  57. xmlNsUri: powerNs,
  58. xmlElement: 'pnode',
  59. xmlChildElement: 'node',
  60. json: 'pnode',
  61. },
  62. {
  63. xmlNs: 'power',
  64. xmlNsUri: powerNs,
  65. xmlElement: 'serviceDeliveryPoint',
  66. xmlChildElement: 'node',
  67. json: 'service-delivery-point',
  68. },
  69. {
  70. xmlNs: 'ei',
  71. xmlNsUri: energyInteropNs,
  72. xmlElement: 'groupID',
  73. json: 'group',
  74. },
  75. {
  76. xmlNs: 'ei',
  77. xmlNsUri: energyInteropNs,
  78. xmlElement: 'groupName',
  79. json: 'group-name',
  80. },
  81. {
  82. xmlNs: 'ei',
  83. xmlNsUri: energyInteropNs,
  84. xmlElement: 'resourceID',
  85. json: 'resource',
  86. },
  87. {
  88. xmlNs: 'ei',
  89. xmlNsUri: energyInteropNs,
  90. xmlElement: 'venID',
  91. json: 'ven',
  92. },
  93. {
  94. xmlNs: 'ei',
  95. xmlNsUri: energyInteropNs,
  96. xmlElement: 'partyID',
  97. json: 'party',
  98. },
  99. ];
  100. const itemBaseMappings = [
  101. {
  102. xmlNs: 'power',
  103. xmlNsUri: powerNs,
  104. xmlElement: 'powerReal',
  105. additionalAttributes: [
  106. {
  107. xmlElement: 'powerAttributes',
  108. json: 'powerAttributes',
  109. },
  110. ],
  111. json: 'power-real',
  112. },
  113. {
  114. xmlNs: 'power',
  115. xmlNsUri: powerNs,
  116. xmlElement: 'powerReactive',
  117. additionalAttributes: [
  118. {
  119. xmlElement: 'powerAttributes',
  120. json: 'powerAttributes',
  121. },
  122. ],
  123. json: 'power-reactive',
  124. },
  125. {
  126. xmlNs: 'power',
  127. xmlNsUri: powerNs,
  128. xmlElement: 'powerApparent',
  129. additionalAttributes: [
  130. {
  131. xmlElement: 'powerAttributes',
  132. json: 'powerAttributes',
  133. },
  134. ],
  135. json: 'power-apparent',
  136. },
  137. {
  138. xmlNs: 'power',
  139. xmlNsUri: powerNs,
  140. xmlElement: 'voltage',
  141. json: 'voltage',
  142. },
  143. {
  144. xmlNs: 'power',
  145. xmlNsUri: powerNs,
  146. xmlElement: 'energyApparent',
  147. json: 'energy-apparent',
  148. },
  149. {
  150. xmlNs: 'power',
  151. xmlNsUri: powerNs,
  152. xmlElement: 'energyReactive',
  153. json: 'energy-reactive',
  154. },
  155. {
  156. xmlNs: 'power',
  157. xmlNsUri: powerNs,
  158. xmlElement: 'energyReal',
  159. json: 'energy-real',
  160. },
  161. {
  162. xmlNs: 'oadr',
  163. xmlNsUri: oadrNs,
  164. xmlElement: 'currencyPerKWh',
  165. json: 'currency-per-kwh',
  166. },
  167. {
  168. xmlNs: 'oadr',
  169. xmlNsUri: oadrNs,
  170. xmlElement: 'currencyPerKW',
  171. json: 'currency-per-kw',
  172. },
  173. {
  174. xmlNs: 'oadr',
  175. xmlNsUri: oadrNs,
  176. xmlElement: 'currencyPerThm',
  177. json: 'currency-per-thm',
  178. },
  179. {
  180. xmlNs: 'oadr',
  181. xmlNsUri: oadrNs,
  182. xmlElement: 'currency',
  183. json: 'currency',
  184. },
  185. {
  186. xmlNs: 'oadr',
  187. xmlNsUri: oadrNs,
  188. xmlElement: 'current',
  189. json: 'current',
  190. },
  191. {
  192. xmlNs: 'oadr',
  193. xmlNsUri: oadrNs,
  194. xmlElement: 'frequency',
  195. json: 'frequency',
  196. },
  197. {
  198. xmlNs: 'oadr',
  199. xmlNsUri: oadrNs,
  200. xmlElement: 'Therm',
  201. json: 'therm',
  202. },
  203. {
  204. xmlNs: 'oadr',
  205. xmlNsUri: oadrNs,
  206. xmlElement: 'temperature',
  207. json: 'temperature',
  208. },
  209. {
  210. xmlNs: 'oadr',
  211. xmlNsUri: oadrNs,
  212. xmlElement: 'pulseCount',
  213. json: 'pulse-count',
  214. },
  215. {
  216. xmlNs: 'oadr',
  217. xmlNsUri: oadrNs,
  218. xmlElement: 'customUnit',
  219. json: 'custom-unit',
  220. },
  221. ];
  222. function parseItemBase(x) {
  223. for (const itemBaseMapping of itemBaseMappings) {
  224. const { json, xmlElement, additionalAttributes } = itemBaseMapping;
  225. const itemBaseList = x[xmlElement];
  226. if (itemBaseList) {
  227. const itemBase = itemBaseList[0]['$$'];
  228. const result = {
  229. type: json,
  230. description: required(
  231. childAttr(itemBase, 'itemDescription'),
  232. 'itemDescription',
  233. ),
  234. units: required(childAttr(itemBase, 'itemUnits'), 'itemUnits'),
  235. siScaleCode: required(
  236. childAttr(itemBase, 'siScaleCode'),
  237. 'siScaleCode',
  238. ),
  239. };
  240. for (const additionalMapping of additionalAttributes || []) {
  241. const unNamespacedAdditionalAttribute = additionalMapping.xmlElement;
  242. const attributesList = itemBase[unNamespacedAdditionalAttribute];
  243. if (attributesList) {
  244. const xmlAttributes = attributesList[0]['$$'];
  245. const attributes = {};
  246. Object.keys(xmlAttributes).forEach(key => {
  247. attributes[key] = required(childAttr(xmlAttributes, key), key);
  248. });
  249. result[additionalMapping.json] = attributes;
  250. }
  251. }
  252. return result;
  253. }
  254. }
  255. }
  256. function serializeItemBase(x) {
  257. for (const itemBaseMapping of itemBaseMappings) {
  258. const {
  259. xmlNs,
  260. xmlNsUri,
  261. json,
  262. xmlElement,
  263. additionalAttributes,
  264. } = itemBaseMapping;
  265. if (x.type === json) {
  266. const result = fragment();
  267. const innerResult = result.ele(xmlNsUri, `${xmlNs}:${xmlElement}`);
  268. innerResult.ele(xmlNsUri, xmlNs + ':itemDescription').txt(x.description);
  269. innerResult.ele(xmlNsUri, xmlNs + ':itemUnits').txt(x.units);
  270. innerResult.ele(siScaleNs, 'scale:siScaleCode').txt(x.siScaleCode);
  271. if (additionalAttributes) {
  272. additionalAttributes.forEach(additionalAttribute => {
  273. const additionalResult = innerResult.ele(
  274. xmlNsUri,
  275. `${xmlNs}:${additionalAttribute.xmlElement}`,
  276. );
  277. const additionalJson = x[additionalAttribute.json];
  278. Object.keys(additionalJson).forEach(key => {
  279. additionalResult
  280. .ele(xmlNsUri, `${xmlNs}:${key}`)
  281. .txt(additionalJson[key]);
  282. });
  283. });
  284. }
  285. return result;
  286. }
  287. }
  288. }
  289. function parseEventDescriptor(eventDescriptor) {
  290. const result = {
  291. eventId: required(childAttr(eventDescriptor, 'eventID'), 'eventID'),
  292. modificationNumber: required(
  293. number(childAttr(eventDescriptor, 'modificationNumber')),
  294. 'modificationNumber',
  295. ),
  296. marketContext: childAttr(
  297. eventDescriptor.eiMarketContext[0]['$$'],
  298. 'marketContext',
  299. ),
  300. createdDateTime: required(
  301. childAttr(eventDescriptor, 'createdDateTime'),
  302. 'createdDateTime',
  303. ),
  304. eventStatus: required(
  305. childAttr(eventDescriptor, 'eventStatus'),
  306. 'eventStatus',
  307. ),
  308. };
  309. const testEvent = boolean(childAttr(eventDescriptor, 'testEvent'));
  310. if (testEvent != null) {
  311. result.testEvent = testEvent;
  312. }
  313. const modificationDateTime = childAttr(
  314. eventDescriptor,
  315. 'modificationDateTime',
  316. );
  317. if (modificationDateTime != null)
  318. result.modificationDateTime = modificationDateTime;
  319. const modificationReason = childAttr(eventDescriptor, 'modificationReason');
  320. if (modificationReason != null)
  321. result.modificationReason = modificationReason;
  322. const priority = number(childAttr(eventDescriptor, 'priority'));
  323. if (priority != null) result.priority = priority;
  324. const vtnComment = childAttr(eventDescriptor, 'vtnComment');
  325. if (vtnComment != null) result.vtnComment = vtnComment;
  326. return result;
  327. }
  328. function serializeEventDescriptor(eventDescriptor) {
  329. const result = fragment();
  330. const eventDescriptorResult = result.ele(
  331. energyInteropNs,
  332. 'ei:eventDescriptor',
  333. );
  334. eventDescriptorResult
  335. .ele(energyInteropNs, 'ei:eventID')
  336. .txt(eventDescriptor.eventId)
  337. .up()
  338. .ele(energyInteropNs, 'ei:modificationNumber')
  339. .txt(eventDescriptor.modificationNumber)
  340. .up()
  341. .ele(energyInteropNs, 'ei:eiMarketContext')
  342. .ele(emixNs, 'emix:marketContext')
  343. .txt(eventDescriptor.marketContext)
  344. .up()
  345. .up()
  346. .ele(energyInteropNs, 'ei:createdDateTime')
  347. .txt(eventDescriptor.createdDateTime)
  348. .up()
  349. .ele(energyInteropNs, 'ei:eventStatus')
  350. .txt(eventDescriptor.eventStatus);
  351. if (eventDescriptor.testEvent != null) {
  352. eventDescriptorResult
  353. .ele(energyInteropNs, 'ei:testEvent')
  354. .txt(eventDescriptor.testEvent);
  355. }
  356. if (eventDescriptor.modificationDateTime != null) {
  357. eventDescriptorResult
  358. .ele(energyInteropNs, 'ei:modificationDateTime')
  359. .txt(eventDescriptor.modificationDateTime);
  360. }
  361. if (eventDescriptor.modificationReason != null) {
  362. eventDescriptorResult
  363. .ele(energyInteropNs, 'ei:modificationReason')
  364. .txt(eventDescriptor.modificationReason);
  365. }
  366. if (eventDescriptor.priority != null) {
  367. eventDescriptorResult
  368. .ele(energyInteropNs, 'ei:priority')
  369. .txt(eventDescriptor.priority);
  370. }
  371. if (eventDescriptor.vtnComment != null) {
  372. eventDescriptorResult
  373. .ele(energyInteropNs, 'ei:vtnComment')
  374. .txt(eventDescriptor.vtnComment);
  375. }
  376. return result;
  377. }
  378. function parseToleranceTolerateStartAfter(tolerance) {
  379. if (tolerance) {
  380. const tolerate = childAttr(tolerance['$$'], 'tolerate');
  381. if (tolerate) {
  382. return duration(tolerate, 'startafter');
  383. }
  384. }
  385. }
  386. function parseEiTarget(eiTarget) {
  387. const result = [];
  388. for (const eiTargetMapping of eiTargetMappings) {
  389. const unNamespacedAttribute = eiTargetMapping.xmlElement;
  390. if (eiTarget[unNamespacedAttribute]) {
  391. const eiTargetValue = eiTarget[unNamespacedAttribute];
  392. let newValues;
  393. if (eiTargetMapping.xmlChildElement) {
  394. const unNamespacedChildAttribute = eiTargetMapping.xmlChildElement;
  395. newValues = eiTargetValue[0]['$$'][unNamespacedChildAttribute];
  396. } else {
  397. newValues = eiTargetValue;
  398. }
  399. const newTargets = newValues.map(value => {
  400. return {
  401. type: eiTargetMapping.json,
  402. value,
  403. };
  404. });
  405. result.push(...newTargets);
  406. }
  407. }
  408. return result;
  409. }
  410. function serializeEiTarget(eiTarget) {
  411. const result = fragment();
  412. const targetElement = result.ele(energyInteropNs, 'ei:eiTarget');
  413. const groupedByType = _.groupBy(eiTarget, item => item.type);
  414. for (const eiTargetMapping of eiTargetMappings) {
  415. const { xmlNs, xmlNsUri, xmlElement, xmlChildElement } = eiTargetMapping;
  416. const byType = groupedByType[eiTargetMapping.json];
  417. if (byType) {
  418. byType.forEach(target => {
  419. if (xmlChildElement) {
  420. targetElement
  421. .ele(xmlNsUri, `${xmlNs}:${xmlElement}`)
  422. .ele(xmlNsUri, `${xmlNs}:${xmlChildElement}`)
  423. .txt(target.value);
  424. } else {
  425. targetElement
  426. .ele(xmlNsUri, `${xmlNs}:${xmlElement}`)
  427. .txt(target.value);
  428. }
  429. });
  430. }
  431. }
  432. return result;
  433. }
  434. function parseEventSignal(eventSignal) {
  435. const result = {
  436. signalName: required(childAttr(eventSignal, 'signalName'), 'signalName'),
  437. signalType: required(childAttr(eventSignal, 'signalType'), 'signalType'),
  438. signalId: required(childAttr(eventSignal, 'signalID'), 'signalID'),
  439. };
  440. const intervals = eventSignal['intervals'][0]['$$'];
  441. if (intervals) {
  442. result.intervals = parseEventSignalIntervals(intervals['interval']);
  443. }
  444. const eiTarget = childAttr(eventSignal, 'eiTarget');
  445. if (eiTarget != null) {
  446. result.targets = parseEiTarget(eiTarget['$$']);
  447. }
  448. const currentValue = childAttr(eventSignal, 'currentValue');
  449. if (currentValue != null)
  450. result.currentValue = parsePayloadFloat(currentValue['$$']);
  451. const itemBase = parseItemBase(eventSignal);
  452. if (itemBase != null) result.itemBase = itemBase;
  453. return result;
  454. }
  455. function serializeEventSignal(eventSignal) {
  456. const result = fragment();
  457. const eiEventSignal = result.ele(energyInteropNs, 'ei:eiEventSignal');
  458. const intervals = eiEventSignal.ele(calendarStreamNs, 'strm:intervals');
  459. serializeEventSignalIntervals(eventSignal.intervals).forEach(interval =>
  460. intervals.import(interval),
  461. );
  462. eiEventSignal.ele(energyInteropNs, 'ei:signalID').txt(eventSignal.signalId);
  463. eiEventSignal
  464. .ele(energyInteropNs, 'ei:signalName')
  465. .txt(eventSignal.signalName);
  466. eiEventSignal
  467. .ele(energyInteropNs, 'ei:signalType')
  468. .txt(eventSignal.signalType);
  469. if (eventSignal.targets) {
  470. eiEventSignal.import(serializeEiTarget(eventSignal.targets));
  471. }
  472. if (eventSignal.currentValue != null) {
  473. eiEventSignal
  474. .ele(energyInteropNs, 'ei:currentValue')
  475. .import(serializePayloadFloat(eventSignal.currentValue));
  476. }
  477. if (eventSignal.itemBase != null) {
  478. eiEventSignal.import(serializeItemBase(eventSignal.itemBase));
  479. }
  480. return result;
  481. }
  482. function parseEventBaseline(eventSignal) {
  483. const result = {
  484. startDate: required(
  485. dateTime(childAttr(eventSignal, 'dtstart'), 'date-time'),
  486. 'dtstart',
  487. ),
  488. duration: required(
  489. duration(childAttr(eventSignal, 'duration'), 'duration'),
  490. 'duration',
  491. ),
  492. baselineId: required(childAttr(eventSignal, 'baselineID'), 'baselineID'),
  493. baselineName: required(
  494. childAttr(eventSignal, 'baselineName'),
  495. 'baselineName',
  496. ),
  497. };
  498. const intervals = eventSignal['intervals'][0]['$$'];
  499. if (intervals) {
  500. result.intervals = parseEventSignalIntervals(intervals['interval']);
  501. }
  502. const itemBase = parseItemBase(eventSignal);
  503. if (itemBase != null) result.itemBase = itemBase;
  504. return result;
  505. }
  506. function serializeEventBaseline(eventBaseline) {
  507. const result = fragment();
  508. const eiEventBaseline = result.ele(energyInteropNs, 'ei:eiEventBaseline');
  509. eiEventBaseline
  510. .ele(energyInteropNs, 'ei:baselineID')
  511. .txt(eventBaseline.baselineId);
  512. eiEventBaseline
  513. .ele(energyInteropNs, 'ei:baselineName')
  514. .txt(eventBaseline.baselineName);
  515. if (eventBaseline.duration) {
  516. eiEventBaseline
  517. .ele(calendarNs, 'cal:duration')
  518. .import(serializeDuration(eventBaseline.duration));
  519. }
  520. if (eventBaseline.startDate) {
  521. eiEventBaseline
  522. .ele(calendarNs, 'cal:dtstart')
  523. .import(serializeDateTime(eventBaseline.startDate));
  524. }
  525. const intervals = eiEventBaseline.ele(calendarStreamNs, 'strm:intervals');
  526. serializeEventSignalIntervals(eventBaseline.intervals).forEach(interval =>
  527. intervals.import(interval),
  528. );
  529. if (eventBaseline.itemBase != null) {
  530. eiEventBaseline.import(serializeItemBase(eventBaseline.itemBase));
  531. }
  532. return result;
  533. }
  534. function parseEiEventSignals(eiEventSignals) {
  535. const wrappedEventSignals = eiEventSignals.eiEventSignal;
  536. const wrappedBaselines = eiEventSignals.eiEventBaseline;
  537. const result = {};
  538. if (wrappedEventSignals) {
  539. result.event = wrappedEventSignals.map(x => parseEventSignal(x['$$']));
  540. }
  541. if (wrappedBaselines) {
  542. result.baseline = wrappedBaselines.map(x => parseEventBaseline(x['$$']))[0];
  543. }
  544. return result;
  545. }
  546. function serializeEiEventSignals(eiEventSignals) {
  547. const eventSignals = eiEventSignals.event.map(x => serializeEventSignal(x));
  548. if (eiEventSignals.baseline) {
  549. return [...eventSignals, serializeEventBaseline(eiEventSignals.baseline)];
  550. }
  551. return eventSignals;
  552. }
  553. function parseEiActivePeriod(activePeriod) {
  554. const properties = activePeriod['properties'][0]['$$'];
  555. const result = {
  556. startDate: required(
  557. dateTime(childAttr(properties, 'dtstart'), 'date-time'),
  558. 'dtstart',
  559. ),
  560. duration: required(
  561. duration(childAttr(properties, 'duration'), 'duration'),
  562. 'duration',
  563. ),
  564. };
  565. const toleranceTolerateStartAfter = parseToleranceTolerateStartAfter(
  566. childAttr(properties, 'tolerance'),
  567. );
  568. if (toleranceTolerateStartAfter != null) {
  569. result.toleranceTolerateStartAfter = toleranceTolerateStartAfter;
  570. }
  571. const notificationDuration = duration(
  572. childAttr(properties, 'x-eiNotification'),
  573. 'duration',
  574. );
  575. if (notificationDuration != null) {
  576. result.notificationDuration = notificationDuration;
  577. }
  578. const rampUpDuration = duration(
  579. childAttr(properties, 'x-eiRampUp'),
  580. 'duration',
  581. );
  582. if (rampUpDuration != null) {
  583. result.rampUpDuration = rampUpDuration;
  584. }
  585. const recoveryDuration = duration(
  586. childAttr(properties, 'x-eiRecovery'),
  587. 'duration',
  588. );
  589. if (recoveryDuration != null) {
  590. result.recoveryDuration = recoveryDuration;
  591. }
  592. return result;
  593. }
  594. function serializeToleranceTolerateStartAfter(duration) {
  595. const result = fragment();
  596. result
  597. .ele(calendarNs, 'cal:tolerance')
  598. .ele(calendarNs, 'cal:tolerate')
  599. .ele(calendarNs, 'cal:startafter')
  600. .txt(duration);
  601. return result;
  602. }
  603. function serializeActivePeriod(activePeriod) {
  604. const result = fragment();
  605. const activePeriodResult = result.ele(energyInteropNs, 'ei:eiActivePeriod');
  606. const properties = activePeriodResult.ele(calendarNs, 'cal:properties');
  607. const components = activePeriodResult.ele(calendarNs, 'cal:components');
  608. components.att(xsiNs, 'xsi:nil', 'true');
  609. properties
  610. .ele(calendarNs, 'cal:dtstart')
  611. .import(serializeDateTime(activePeriod.startDate))
  612. .up()
  613. .ele(calendarNs, 'cal:duration')
  614. .import(serializeDuration(activePeriod.duration))
  615. .up();
  616. if (activePeriod.toleranceTolerateStartAfter) {
  617. properties.import(
  618. serializeToleranceTolerateStartAfter(
  619. activePeriod.toleranceTolerateStartAfter,
  620. ),
  621. );
  622. }
  623. if (activePeriod.notificationDuration) {
  624. properties
  625. .ele(energyInteropNs, 'ei:x-eiNotification')
  626. .import(serializeDuration(activePeriod.notificationDuration));
  627. }
  628. if (activePeriod.rampUpDuration) {
  629. properties
  630. .ele(energyInteropNs, 'ei:x-eiRampUp')
  631. .import(serializeDuration(activePeriod.rampUpDuration));
  632. }
  633. if (activePeriod.recoveryDuration) {
  634. properties
  635. .ele(energyInteropNs, 'ei:x-eiRecovery')
  636. .import(serializeDuration(activePeriod.recoveryDuration));
  637. }
  638. return result;
  639. }
  640. function parseEiEvent(eiEvent) {
  641. return {
  642. eventDescriptor: parseEventDescriptor(eiEvent.eventDescriptor[0]['$$']),
  643. activePeriod: parseEiActivePeriod(eiEvent.eiActivePeriod[0]['$$']),
  644. signals: parseEiEventSignals(eiEvent.eiEventSignals[0]['$$']),
  645. targets: parseEiTarget(eiEvent.eiTarget[0]['$$']),
  646. };
  647. }
  648. function serializeEiEvent(eiEvent) {
  649. const result = fragment();
  650. const eiEventResult = result.ele(energyInteropNs, 'ei:eiEvent');
  651. eiEventResult.import(serializeEventDescriptor(eiEvent.eventDescriptor));
  652. eiEventResult.import(serializeActivePeriod(eiEvent.activePeriod));
  653. const eiEventSignals = eiEventResult.ele(
  654. energyInteropNs,
  655. 'ei:eiEventSignals',
  656. );
  657. serializeEiEventSignals(eiEvent.signals).forEach(signal =>
  658. eiEventSignals.import(signal),
  659. );
  660. if (eiEvent.targets) {
  661. eiEventResult.import(serializeEiTarget(eiEvent.targets));
  662. }
  663. return result;
  664. }
  665. function parseEvents(eventList) {
  666. const events = [];
  667. for (const wrappedEvent of eventList) {
  668. const event = wrappedEvent['$$'];
  669. const parsedEiEvent = parseEiEvent(event.eiEvent[0]['$$']);
  670. parsedEiEvent.responseRequired = childAttr(event, 'oadrResponseRequired');
  671. events.push(parsedEiEvent);
  672. }
  673. return events;
  674. }
  675. async function parse(input) {
  676. const json = await parseXML(input);
  677. const o =
  678. json['oadrPayload']['$$']['oadrSignedObject'][0]['$$'][
  679. 'oadrDistributeEvent'
  680. ][0]['$$'];
  681. const result = {
  682. _type: 'oadrDistributeEvent',
  683. };
  684. if (o['eiResponse']) {
  685. const { code, description, requestId } = parseEiResponse(
  686. o['eiResponse'][0]['$$'],
  687. );
  688. result.responseCode = code;
  689. result.responseRequestId = requestId;
  690. if (description != null) result.responseDescription = description;
  691. if (code < 200 || code >= 300) {
  692. return result;
  693. }
  694. }
  695. const payloadRequestId = childAttr(o, 'requestID');
  696. if (payloadRequestId != null) result.requestId = payloadRequestId;
  697. result.vtnId = required(childAttr(o, 'vtnID'), 'vtnID');
  698. const events = parseEvents(o['oadrEvent'] || []);
  699. result.events = events;
  700. return result;
  701. }
  702. function validate(obj) {
  703. if (obj.responseCode == null) {
  704. throw new Error('Missing responseCode');
  705. }
  706. if (obj.responseRequestId == null) {
  707. throw new Error('Missing responseRequestId');
  708. }
  709. }
  710. function serializeOadrEvent(event) {
  711. const result = fragment();
  712. const oadrEvent = result.ele(oadrNs, 'oadr2b:oadrEvent');
  713. oadrEvent.import(serializeEiEvent(event));
  714. oadrEvent
  715. .ele(oadrNs, 'oadr2b:oadrResponseRequired')
  716. .txt(event.responseRequired);
  717. return result;
  718. }
  719. function serializeEvents(events) {
  720. const result = fragment();
  721. events.forEach(event => {
  722. result.import(serializeOadrEvent(event));
  723. });
  724. return result;
  725. }
  726. function serialize(obj) {
  727. validate(obj);
  728. const vtnId =
  729. obj.vtnId != null
  730. ? fragment()
  731. .ele(energyInteropNs, 'ei:vtnID')
  732. .txt(obj.vtnId)
  733. : fragment();
  734. const requestId =
  735. obj.requestId != null
  736. ? fragment()
  737. .ele(energyInteropPayloadsNs, 'pyld:requestID')
  738. .txt(obj.requestId)
  739. : fragment();
  740. const doc = createDoc()
  741. .ele('@oadr2b', 'oadr2b:oadrPayload')
  742. .ele('oadr2b:oadrSignedObject')
  743. .ele('oadr2b:oadrDistributeEvent')
  744. .att('@ei', 'ei:schemaVersion', '2.0b')
  745. .import(serializeEiResponse(obj))
  746. .import(vtnId)
  747. .import(requestId)
  748. .import(serializeEvents(obj.events))
  749. .doc();
  750. return doc.end({ headless: true, prettyPrint: false });
  751. }
  752. async function canParse(input) {
  753. const json = await parseXML(input);
  754. const o = json['oadrPayload']['$$']['oadrSignedObject'][0]['$$'];
  755. return o['oadrDistributeEvent'] != null;
  756. }
  757. function canSerialize(message) {
  758. return message._type === 'oadrDistributeEvent';
  759. }
  760. module.exports = {
  761. parse,
  762. serialize,
  763. canParse,
  764. canSerialize,
  765. };