Apply Upstream code (2021-03-15)
[platform/upstream/connectedhomeip.git] / src / app / zap-templates / templates / chip / helper.js
1 /*
2  *
3  *    Copyright (c) 2020-2021 Project CHIP Authors
4  *
5  *    Licensed under the Apache License, Version 2.0 (the "License");
6  *    you may not use this file except in compliance with the License.
7  *    You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *    Unless required by applicable law or agreed to in writing, software
12  *    distributed under the License is distributed on an "AS IS" BASIS,
13  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *    See the License for the specific language governing permissions and
15  *    limitations under the License.
16  */
17
18 // Import helpers from zap core
19 const zapPath      = '../../../../../third_party/zap/repo/src-electron/';
20 const queryConfig  = require(zapPath + 'db/query-config.js')
21 const queryImpexp  = require(zapPath + 'db/query-impexp.js')
22 const templateUtil = require(zapPath + 'generator/template-util.js')
23 const zclHelper    = require(zapPath + 'generator/helper-zcl.js')
24 const zclQuery     = require(zapPath + 'db/query-zcl.js')
25
26 const StringHelper    = require('../../common/StringHelper.js');
27 const ChipTypesHelper = require('../../common/ChipTypesHelper.js');
28
29 /**
30  * This method converts a ZCL type to the length expected for the
31  * BufferWriter.Put method.
32  * TODO
33  * Not all types are supported at the moment, so if there is any unsupported type
34  * that we are trying to convert, it will throw an error.
35  */
36 function asPutLength(zclType)
37 {
38   const type = ChipTypesHelper.asBasicType(zclType);
39   switch (type) {
40   case 'int8_t':
41   case 'int16_t':
42   case 'int32_t':
43   case 'int64_t':
44   case 'uint8_t':
45   case 'uint16_t':
46   case 'uint32_t':
47   case 'uint64_t':
48     return type.replace(/[^0-9]/g, '');
49   default:
50     throw error = 'asPutLength: Unhandled type: ' + zclType;
51   }
52 }
53
54 function asPutCastType(zclType)
55 {
56   const type = ChipTypesHelper.asBasicType(zclType);
57   switch (type) {
58   case 'int8_t':
59   case 'int16_t':
60   case 'int32_t':
61   case 'int64_t':
62     return 'u' + type;
63   case 'uint8_t':
64   case 'uint16_t':
65   case 'uint32_t':
66   case 'uint64_t':
67     return type;
68   default:
69     throw error = 'asPutCastType: Unhandled type: ' + zclType;
70   }
71 }
72
73 /**
74  * Creates block iterator over the enabled server side clusters
75  *
76  * @param {*} options
77  */
78 function chip_server_clusters(options)
79 {
80   const db = this.global.db;
81
82   return queryImpexp.exportendPointTypeIds(db, this.global.sessionId)
83       .then(endpointTypes => { return zclQuery.exportAllClustersDetailsFromEndpointTypes(db, endpointTypes) })
84       .then(clusters => clusters.filter(cluster => cluster.enabled == 1 && cluster.side == 'server'))
85       .then(clusters => templateUtil.collectBlocks(clusters, options, this))
86 }
87
88 function chip_clusters(options)
89 {
90   const db = this.global.db;
91
92   return queryImpexp.exportendPointTypeIds(db, this.global.sessionId)
93       .then(endpointTypes => { return zclQuery.exportAllClustersDetailsFromEndpointTypes(db, endpointTypes) })
94       .then(clusters => clusters.filter(cluster => cluster.enabled == 1))
95       .then(clusters => templateUtil.collectBlocks(clusters, options, this))
96 }
97
98 /**
99  * Creates block iterator over the server side cluster command
100  * for a given cluster.
101  *
102  * This function is meant to be used inside a {{#chip_server_clusters}}
103  * block. It will throws otherwise.
104  *
105  * @param {*} options
106  */
107 function chip_server_cluster_commands(options)
108 {
109   // Retrieve the clusterName and the clusterSide. If any if not available, an error will be thrown.
110   const clusterName = this.name;
111   const clusterSide = this.side;
112   if (clusterName == undefined || clusterSide == undefined) {
113     const error = 'chip_server_cluster_commands: Could not find relevant parent cluster.';
114     console.log(error);
115     throw error;
116   }
117
118   function filterCommand(cmd)
119   {
120     return cmd.clusterName == clusterName && cmd.clusterSide == 'client' && cmd.name.includes('Response') == false;
121   }
122
123   const db = this.global.db;
124   return queryImpexp.exportendPointTypeIds(db, this.global.sessionId)
125       .then(endpointTypes => zclQuery.exportClustersAndEndpointDetailsFromEndpointTypes(db, endpointTypes))
126       .then(endpointsAndClusters => zclQuery.exportCommandDetailsFromAllEndpointTypesAndClusters(db, endpointsAndClusters))
127       .then(endpointCommands => endpointCommands.filter(filterCommand))
128       .then(endpointCommands => templateUtil.collectBlocks(endpointCommands, options, this))
129 }
130
131 /**
132  * Creates block iterator over the server side cluster command arguments
133  * for a given command.
134  *
135  * This function is meant to be used inside a {{#chip_server_cluster_commands}}
136  * block. It will throws otherwise.
137  *
138  * @param {*} options
139  */
140 function chip_server_cluster_command_arguments(options)
141 {
142   const db = this.global.db;
143
144   function collectItem(arg, pkgId)
145   {
146     return zclHelper.isStruct(db, arg.type, pkgId).then(result => {
147       if (result == 'unknown') {
148         return arg;
149       }
150
151       return zclQuery.selectStructByName(db, arg.type, pkgId).then(rec => {
152         return zclQuery.selectAllStructItemsById(db, rec.id).then(items => items.map(item => {
153           item.name = item.label;
154           return item;
155         }));
156       })
157     })
158   }
159
160   function collectItems(args, pkgId)
161   {
162     return Promise.all(args.map(arg => collectItem.call(this, arg, pkgId))).then(items => items.flat()).then(items => {
163       return Promise.all(items.map(item => {
164         if (StringHelper.isString(item.type)) {
165           item.chipType = 'chip::ByteSpan';
166           return item;
167         }
168
169         return zclHelper.asUnderlyingZclType.call(this, item.type, options).then(zclType => {
170           // Enhanced the command argument with 'chipType', 'chipTypePutLength', 'chipTypePutCastType' for conveniences.
171           item.chipType            = zclType;
172           item.chipTypePutLength   = asPutLength(zclType);
173           item.chipTypePutCastType = asPutCastType(zclType);
174           return item;
175         })
176       }));
177     });
178   }
179
180   function fn(pkgId)
181   {
182     return zclQuery.selectCommandArgumentsByCommandId(db, this.id, pkgId)
183         .then(args => collectItems.call(this, args, pkgId))
184         .then(items => templateUtil.collectBlocks(items, options, this));
185   }
186
187   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
188   return templateUtil.templatePromise(this.global, promise)
189 }
190
191 /**
192  * Returns if a given command argument chip type is signed.
193  *
194  * This function is meant to be used inside a {{#chip_*}} block.
195  * It will throws otherwise.
196  *
197  * @param {*} options
198  */
199 function isSignedType()
200 {
201   const type = this.chipType;
202   if (!type) {
203     const error = 'isSignedType: Could not find chipType';
204     console.log(error);
205     throw error;
206   }
207
208   switch (type) {
209   case 'int8_t':
210   case 'int16_t':
211   case 'int32_t':
212   case 'int64_t':
213     return true;
214   default:
215     return false;
216   }
217 }
218
219 /**
220  * Returns if a given command argument chip type is discrete.
221  *
222  * This function is meant to be used inside a {{#chip_*}} block.
223  * It will throws otherwise.
224  *
225  * @param {*} options
226  */
227 function isDiscreteType()
228 {
229   const type = this.chipType;
230   if (!type) {
231     const error = 'isDiscreteType: Could not find chipType';
232     console.log(error);
233     throw error;
234   }
235
236   return this.discrete;
237 }
238
239 function getAttributes(pkgId, options)
240 {
241   const db = this.global.db;
242   return queryConfig.getAllSessionAttributes(db, this.global.sessionId).then(atts => {
243     return Promise.all(atts.map(att => zclQuery.selectAtomicByName(db, att.type, pkgId).then(atomic => {
244       // Enhanced the attribute with 'atomidId', 'discrete', chipType properties for convenience.
245       att.atomicTypeId = atomic.atomicId;
246       att.discrete     = atomic.discrete;
247
248       if (StringHelper.isString(att.type)) {
249         // Enhanced the command argument with 'chipType' for conveniences.
250         att.chipType = 'chip::ByteSpan';
251         return att;
252       }
253
254       return zclHelper.asUnderlyingZclType.call(this, att.type, options).then(zclType => {
255         att.chipType = zclType;
256         return att;
257       });
258     })));
259   })
260 }
261
262 /**
263  * Creates block iterator over the server side cluster attributes
264  * for a given cluster.
265  *
266  * This function is meant to be used inside a {{#chip_server_clusters}}
267  * block. It will throws otherwise.
268  *
269  * @param {*} options
270  */
271 function chip_server_cluster_attributes(options)
272 {
273   // Retrieve the clusterCode and the clusterSide. If any if not available, an error will be thrown.
274   const clusterCode = this.code;
275   const clusterSide = this.side;
276   if (clusterCode == undefined || clusterSide == undefined) {
277     const error = 'chip_server_cluster_attributes: Could not find relevant parent cluster.';
278     console.log(error);
279     throw error;
280   }
281
282   function fn(pkgId)
283   {
284     return getAttributes.call(this, pkgId, options).then(atts => {
285       atts = atts.filter(att => att.clusterCode == clusterCode && att.side == 'server');
286       atts.forEach(att => {
287         const sameAttributes = atts.filter(att2 => att.name == att2.name);
288         let isWritable       = !!sameAttributes.find(att2 => att2.writable);
289         let isReportable     = !!sameAttributes.find(att2 => att2.reportable.included);
290         if (isWritable || isReportable) {
291           if (!StringHelper.isString(att.type)) {
292             att.chipTypePutLength   = asPutLength(att.chipType);
293             att.chipTypePutCastType = asPutCastType(att.chipType);
294           }
295           att.writable            = isWritable;
296           att.reportable.included = isReportable;
297         }
298       })
299       atts = atts.filter((att, index) => atts.findIndex(att2 => att.name == att2.name) == index);
300       return templateUtil.collectBlocks(atts, options, this);
301     })
302   }
303
304   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
305   return templateUtil.templatePromise(this.global, promise);
306 }
307
308 /**
309  * Returns if a given attribute is writable.
310  *
311  * This function is meant to be used inside a {{#chip_server_cluster_attributes}} block.
312  * It will throws otherwise.
313  *
314  * @param {*} options
315  */
316 function isWritableAttribute(options)
317 {
318   if (this.attributeCode == undefined) {
319     const error = 'isWritableAttribute: missing attribute code.';
320     console.log(error);
321     throw error;
322   }
323
324   return this.writable == 1;
325 }
326
327 /**
328  * Returns if a given attribute is reportable.
329  *
330  * This function is meant to be used inside a {{#chip_server_cluster_attributes}} block.
331  * It will throws otherwise.
332  *
333  * @param {*} options
334  */
335 function isReportableAttribute(options)
336 {
337   if (this.attributeCode == undefined) {
338     const error = 'isReportableAttribute: missing attribute code.';
339     console.log(error);
340     throw error;
341   }
342
343   return this.reportable.included == 1;
344 }
345
346 /**
347  * Returns if a given command is manufacturer specific
348  *
349  * This function is meant to be used inside a {{#chip_server_cluster_commands}} block.
350  * It will throws otherwise.
351  *
352  * @param {*} options
353  */
354 function isManufacturerSpecificCommand()
355 {
356   if (this.commandSource == undefined) {
357     const error = 'isManufacturerSpecificCommand: Not inside a ({#chip_server_cluster_commands}} block.';
358     console.log(error);
359     throw error;
360   }
361
362   return !!this.mfgCode;
363 }
364
365 function asPythonType(zclType)
366 {
367   const type = ChipTypesHelper.asBasicType(zclType);
368   switch (type) {
369   case 'int8_t':
370   case 'int16_t':
371   case 'int32_t':
372   case 'int64_t':
373   case 'uint8_t':
374   case 'uint16_t':
375   case 'uint32_t':
376   case 'uint64_t':
377     return 'int';
378   case 'char *':
379     return 'str';
380   case 'uint8_t *':
381   case 'chip::ByteSpan':
382     return 'bytes'
383   }
384 }
385
386 function asPythonCType(zclType)
387 {
388   const type = ChipTypesHelper.asBasicType(zclType);
389   switch (type) {
390   case 'int8_t':
391   case 'int16_t':
392   case 'int32_t':
393   case 'int64_t':
394   case 'uint8_t':
395   case 'uint16_t':
396   case 'uint32_t':
397   case 'uint64_t':
398     return 'c_' + type.replace('_t', '');
399   case 'char *':
400   case 'uint8_t *':
401     return 'c_char_p';
402   }
403 }
404
405 function hasSpecificResponse(commandName)
406 {
407   // Retrieve the clusterName and the clusterSide. If any if not available, an error will be thrown.
408   const clusterName = this.parent.name;
409   const clusterSide = this.parent.side;
410   if (clusterName == undefined || clusterSide == undefined) {
411     const error = 'chip_server_cluster_commands: Could not find relevant parent cluster.';
412     console.log(error);
413     throw error;
414   }
415
416   function filterCommand(cmd)
417   {
418     return cmd.clusterName == clusterName && cmd.name == (commandName + "Response");
419   }
420
421   function fn(pkgId)
422   {
423     const db = this.global.db;
424     return queryImpexp.exportendPointTypeIds(db, this.global.sessionId)
425         .then(endpointTypes => zclQuery.exportClustersAndEndpointDetailsFromEndpointTypes(db, endpointTypes))
426         .then(endpointsAndClusters => zclQuery.exportCommandDetailsFromAllEndpointTypesAndClusters(db, endpointsAndClusters))
427         .then(endpointCommands => endpointCommands.filter(filterCommand).length)
428   }
429
430   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
431   return templateUtil.templatePromise(this.global, promise);
432 }
433
434 function asCallbackAttributeType(attributeType)
435 {
436   switch (parseInt(attributeType)) {
437   case 0x00: // nodata / No data
438   case 0x0A: // data24 / 24-bit data
439   case 0x0C: // data40 / 40-bit data
440   case 0x0D: // data48 / 48-bit data
441   case 0x0E: // data56 / 56-bit data
442   case 0x1A: // map24 / 24-bit bitmap
443   case 0x1C: // map40 / 40-bit bitmap
444   case 0x1D: // map48 / 48-bit bitmap
445   case 0x1E: // map56 / 56-bit bitmap
446   case 0x22: // uint24 / Unsigned 24-bit integer
447   case 0x24: // uint40 / Unsigned 40-bit integer
448   case 0x25: // uint48 / Unsigned 48-bit integer
449   case 0x26: // uint56 / Unsigned 56-bit integer
450   case 0x2A: // int24 / Signed 24-bit integer
451   case 0x2C: // int40 / Signed 40-bit integer
452   case 0x2D: // int48 / Signed 48-bit integer
453   case 0x2E: // int56 / Signed 56-bit integer
454   case 0x38: // semi / Semi-precision
455   case 0x39: // single / Single precision
456   case 0x3A: // double / Double precision
457   case 0x48: // array / Array
458   case 0x49: // struct / Structure
459   case 0x50: // set / Set
460   case 0x51: // bag / Bag
461   case 0xE0: // ToD / Time of day
462   case 0xEA: // bacOID / BACnet OID
463   case 0xF1: // key128 / 128-bit security key
464   case 0xFF: // unk / Unknown
465     return 'Unsupported';
466   case 0x41: // octstr / Octet string
467   case 0x42: // string / Character string
468   case 0x43: // octstr16 / Long octet string
469   case 0x44: // string16 / Long character string
470     return 'String';
471   case 0x08: // data8 / 8-bit data
472   case 0x18: // map8 / 8-bit bitmap
473   case 0x20: // uint8 / Unsigned  8-bit integer
474   case 0x30: // enum8 / 8-bit enumeration
475     return 'Int8u';
476   case 0x09: // data16 / 16-bit data
477   case 0x19: // map16 / 16-bit bitmap
478   case 0x21: // uint16 / Unsigned 16-bit integer
479   case 0x31: // enum16 / 16-bit enumeration
480   case 0xE8: // clusterId / Cluster ID
481   case 0xE9: // attribId / Attribute ID
482     return 'Int16u';
483   case 0x0B: // data32 / 32-bit data
484   case 0x1B: // map32 / 32-bit bitmap
485   case 0x23: // uint32 / Unsigned 32-bit integer
486   case 0xE1: // date / Date
487   case 0xE2: // UTC / UTCTime
488     return 'Int32u';
489   case 0x0F: // data64 / 64-bit data
490   case 0x1F: // map64 / 64-bit bitmap
491   case 0x27: // uint64 / Unsigned 64-bit integer
492   case 0xF0: // EUI64 / IEEE address
493     return 'Int64u';
494   case 0x10: // bool / Boolean
495     return 'Boolean';
496   case 0x28: // int8 / Signed 8-bit integer
497     return 'Int8s';
498   case 0x29: // int16 / Signed 16-bit integer
499     return 'Int16s';
500   case 0x2B: // int32 / Signed 32-bit integer
501     return 'Int32s';
502   case 0x2F: // int64 / Signed 64-bit integer
503     return 'Int64s';
504   default:
505     error = 'Unhandled attribute type ' + attributeType;
506     throw error;
507   }
508 }
509
510 function asObjectiveCBasicType(type)
511 {
512   if (StringHelper.isOctetString(type)) {
513     return 'NSData *';
514   } else if (StringHelper.isCharString(type)) {
515     return 'NSString *';
516   } else {
517     return ChipTypesHelper.asBasicType(this.chipType);
518   }
519 }
520
521 function asObjectiveCNumberType(label, type)
522 {
523   function fn(pkgId)
524   {
525     const options = { 'hash' : {} };
526     return zclHelper.asUnderlyingZclType.call(this, type, options).then(zclType => {
527       switch (zclType) {
528       case 'uint8_t':
529         return 'UnsignedChar';
530       case 'uint16_t':
531         return 'UnsignedShort';
532       case 'uint32_t':
533         return 'UnsignedLong';
534       case 'uint64_t':
535         return 'UnsignedLongLong';
536       case 'int8_t':
537         return 'Char';
538       case 'int16_t':
539         return 'Short';
540       case 'int32_t':
541         return 'Long';
542       case 'int64_t':
543         return 'LongLong';
544       default:
545         error = label + ': Unhandled underlying type ' + zclType + ' for original type ' + type;
546         throw error;
547       }
548     })
549   }
550
551   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
552   return templateUtil.templatePromise(this.global, promise)
553 }
554
555 function isStrEndsWith(str, substr)
556 {
557   return str.endsWith(substr);
558 }
559
560 //
561 // Module exports
562 //
563 exports.chip_clusters                         = chip_clusters;
564 exports.chip_server_clusters                  = chip_server_clusters;
565 exports.chip_server_cluster_commands          = chip_server_cluster_commands;
566 exports.chip_server_cluster_command_arguments = chip_server_cluster_command_arguments
567 exports.asBasicType                           = ChipTypesHelper.asBasicType;
568 exports.asObjectiveCBasicType                 = asObjectiveCBasicType;
569 exports.asObjectiveCNumberType                = asObjectiveCNumberType;
570 exports.isSignedType                          = isSignedType;
571 exports.isDiscreteType                        = isDiscreteType;
572 exports.chip_server_cluster_attributes        = chip_server_cluster_attributes;
573 exports.isWritableAttribute                   = isWritableAttribute;
574 exports.isReportableAttribute                 = isReportableAttribute;
575 exports.isManufacturerSpecificCommand         = isManufacturerSpecificCommand;
576 exports.asPythonType                          = asPythonType;
577 exports.asPythonCType                         = asPythonCType;
578 exports.asCallbackAttributeType               = asCallbackAttributeType;
579 exports.hasSpecificResponse                   = hasSpecificResponse;
580 exports.isStrEndsWith                         = isStrEndsWith;