distribute-event.js 25 KB

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