distribute-event.js 21 KB

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