2e6871597314ac179ad889cf241c5e6d9006e372
[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 = '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 = '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.isByteString(item.type)) {
165           item.chipType = 'chip::ByteSpan';
166           return item;
167         } else if (StringHelper.isString(item.type)) {
168           // Enhanced the command argument with 'chipType' for conveniences.
169           item.chipType = 'char *';
170           return item;
171         }
172
173         return zclHelper.asUnderlyingZclType.call(this, item.type, options).then(zclType => {
174           // Enhanced the command argument with 'chipType', 'chipTypePutLength', 'chipTypePutCastType' for conveniences.
175           item.chipType            = zclType;
176           item.chipTypePutLength   = asPutLength(zclType);
177           item.chipTypePutCastType = asPutCastType(zclType);
178           return item;
179         })
180       }));
181     });
182   }
183
184   function fn(pkgId)
185   {
186     return zclQuery.selectCommandArgumentsByCommandId(db, this.id, pkgId)
187         .then(args => collectItems.call(this, args, pkgId))
188         .then(items => templateUtil.collectBlocks(items, options, this));
189   }
190
191   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
192   return templateUtil.templatePromise(this.global, promise)
193 }
194
195 /**
196  * Returns if a given command argument chip type is signed.
197  *
198  * This function is meant to be used inside a {{#chip_*}} block.
199  * It will throws otherwise.
200  *
201  * @param {*} options
202  */
203 function isSignedType()
204 {
205   const type = this.chipType;
206   if (!type) {
207     const error = 'isSignedType: Could not find chipType';
208     console.log(error);
209     throw error;
210   }
211
212   switch (type) {
213   case 'int8_t':
214   case 'int16_t':
215   case 'int32_t':
216   case 'int64_t':
217     return true;
218   default:
219     return false;
220   }
221 }
222
223 /**
224  * Returns if a given command argument chip type is discrete.
225  *
226  * This function is meant to be used inside a {{#chip_*}} block.
227  * It will throws otherwise.
228  *
229  * @param {*} options
230  */
231 function isDiscreteType()
232 {
233   const type = this.chipType;
234   if (!type) {
235     const error = 'isDiscreteType: Could not find chipType';
236     console.log(error);
237     throw error;
238   }
239
240   return this.discrete;
241 }
242
243 function getAttributes(pkgId, options)
244 {
245   const db = this.global.db;
246   return queryConfig.getAllSessionAttributes(db, this.global.sessionId).then(atts => {
247     return Promise.all(atts.map(att => zclQuery.selectAtomicByName(db, att.type, pkgId).then(atomic => {
248       // Enhanced the attribute with 'atomidId', 'discrete', chipType properties for convenience.
249       att.atomicTypeId = atomic.atomicId;
250       att.discrete     = atomic.discrete;
251       return zclHelper.asUnderlyingZclType.call(this, att.type, options).then(zclType => {
252         att.chipType = zclType;
253         return att;
254       });
255     })));
256   })
257 }
258
259 /**
260  * Creates block iterator over the server side cluster attributes
261  * for a given cluster.
262  *
263  * This function is meant to be used inside a {{#chip_server_clusters}}
264  * block. It will throws otherwise.
265  *
266  * @param {*} options
267  */
268 function chip_server_cluster_attributes(options)
269 {
270   // Retrieve the clusterCode and the clusterSide. If any if not available, an error will be thrown.
271   const clusterCode = this.code;
272   const clusterSide = this.side;
273   if (clusterCode == undefined || clusterSide == undefined) {
274     const error = 'chip_server_cluster_attributes: Could not find relevant parent cluster.';
275     console.log(error);
276     throw error;
277   }
278
279   function fn(pkgId)
280   {
281     return getAttributes.call(this, pkgId, options).then(atts => {
282       atts = atts.filter(att => att.clusterCode == clusterCode && att.side == 'server');
283       atts.forEach(att => {
284         const sameAttributes = atts.filter(att2 => att.name == att2.name);
285         let isWritable       = !!sameAttributes.find(att2 => att2.writable);
286         let isReportable     = !!sameAttributes.find(att2 => att2.reportable.included);
287         if (isWritable || isReportable) {
288           att.chipTypePutLength   = asPutLength(att.chipType);
289           att.chipTypePutCastType = asPutCastType(att.chipType);
290           att.writable            = isWritable;
291           att.reportable.included = isReportable;
292         }
293       })
294       atts = atts.filter((att, index) => atts.findIndex(att2 => att.name == att2.name) == index);
295       return templateUtil.collectBlocks(atts, options, this);
296     })
297   }
298
299   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
300   return templateUtil.templatePromise(this.global, promise);
301 }
302
303 /**
304  * Returns if a given attribute is writable.
305  *
306  * This function is meant to be used inside a {{#chip_server_cluster_attributes}} block.
307  * It will throws otherwise.
308  *
309  * @param {*} options
310  */
311 function isWritableAttribute(options)
312 {
313   if (this.attributeCode == undefined) {
314     const error = 'isWritableAttribute: missing attribute code.';
315     console.log(error);
316     throw error;
317   }
318
319   return this.writable == 1;
320 }
321
322 /**
323  * Returns if a given attribute is reportable.
324  *
325  * This function is meant to be used inside a {{#chip_server_cluster_attributes}} block.
326  * It will throws otherwise.
327  *
328  * @param {*} options
329  */
330 function isReportableAttribute(options)
331 {
332   if (this.attributeCode == undefined) {
333     const error = 'isReportableAttribute: missing attribute code.';
334     console.log(error);
335     throw error;
336   }
337
338   return this.reportable.included == 1;
339 }
340
341 /**
342  * Returns if a given command is manufacturer specific
343  *
344  * This function is meant to be used inside a {{#chip_server_cluster_commands}} block.
345  * It will throws otherwise.
346  *
347  * @param {*} options
348  */
349 function isManufacturerSpecificCommand()
350 {
351   if (this.commandSource == undefined) {
352     const error = 'isManufacturerSpecificCommand: Not inside a ({#chip_server_cluster_commands}} block.';
353     console.log(error);
354     throw error;
355   }
356
357   return !!this.mfgCode;
358 }
359
360 function asPythonType(zclType)
361 {
362   const type = ChipTypesHelper.asBasicType(zclType);
363   switch (type) {
364   case 'int8_t':
365   case 'int16_t':
366   case 'int32_t':
367   case 'int64_t':
368   case 'uint8_t':
369   case 'uint16_t':
370   case 'uint32_t':
371   case 'uint64_t':
372     return 'int';
373   case 'char *':
374     return 'str';
375   case 'uint8_t *':
376   case 'chip::ByteSpan':
377     return 'bytes'
378   }
379 }
380
381 function asPythonCType(zclType)
382 {
383   const type = ChipTypesHelper.asBasicType(zclType);
384   switch (type) {
385   case 'int8_t':
386   case 'int16_t':
387   case 'int32_t':
388   case 'int64_t':
389   case 'uint8_t':
390   case 'uint16_t':
391   case 'uint32_t':
392   case 'uint64_t':
393     return 'c_' + type.replace('_t', '');
394   case 'char *':
395   case 'uint8_t *':
396     return 'c_char_p';
397   }
398 }
399
400 function hasSpecificResponse(commandName)
401 {
402   // Retrieve the clusterName and the clusterSide. If any if not available, an error will be thrown.
403   const clusterName = this.parent.name;
404   const clusterSide = this.parent.side;
405   if (clusterName == undefined || clusterSide == undefined) {
406     const error = 'chip_server_cluster_commands: Could not find relevant parent cluster.';
407     console.log(error);
408     throw error;
409   }
410
411   function filterCommand(cmd)
412   {
413     return cmd.clusterName == clusterName && cmd.name == (commandName + "Response");
414   }
415
416   function fn(pkgId)
417   {
418     const db = this.global.db;
419     return queryImpexp.exportendPointTypeIds(db, this.global.sessionId)
420         .then(endpointTypes => zclQuery.exportClustersAndEndpointDetailsFromEndpointTypes(db, endpointTypes))
421         .then(endpointsAndClusters => zclQuery.exportCommandDetailsFromAllEndpointTypesAndClusters(db, endpointsAndClusters))
422         .then(endpointCommands => endpointCommands.filter(filterCommand).length)
423   }
424
425   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
426   return templateUtil.templatePromise(this.global, promise);
427 }
428
429 function asCallbackAttributeType(attributeType)
430 {
431   switch (parseInt(attributeType)) {
432   case 0x00: // nodata / No data
433   case 0x0A: // data24 / 24-bit data
434   case 0x0C: // data40 / 40-bit data
435   case 0x0D: // data48 / 48-bit data
436   case 0x0E: // data56 / 56-bit data
437   case 0x1A: // map24 / 24-bit bitmap
438   case 0x1C: // map40 / 40-bit bitmap
439   case 0x1D: // map48 / 48-bit bitmap
440   case 0x1E: // map56 / 56-bit bitmap
441   case 0x22: // uint24 / Unsigned 24-bit integer
442   case 0x24: // uint40 / Unsigned 40-bit integer
443   case 0x25: // uint48 / Unsigned 48-bit integer
444   case 0x26: // uint56 / Unsigned 56-bit integer
445   case 0x2A: // int24 / Signed 24-bit integer
446   case 0x2C: // int40 / Signed 40-bit integer
447   case 0x2D: // int48 / Signed 48-bit integer
448   case 0x2E: // int56 / Signed 56-bit integer
449   case 0x38: // semi / Semi-precision
450   case 0x39: // single / Single precision
451   case 0x3A: // double / Double precision
452   case 0x41: // octstr / Octet string
453   case 0x42: // string / Character string
454   case 0x43: // octstr16 / Long octet string
455   case 0x44: // string16 / Long character string
456   case 0x48: // array / Array
457   case 0x49: // struct / Structure
458   case 0x50: // set / Set
459   case 0x51: // bag / Bag
460   case 0xE0: // ToD / Time of day
461     return 'Unsupported';
462   case 0x08: // data8 / 8-bit data
463   case 0x18: // map8 / 8-bit bitmap
464   case 0x20: // uint8 / Unsigned  8-bit integer
465   case 0x30: // enum8 / 8-bit enumeration
466     return 'Int8u';
467   case 0x09: // data16 / 16-bit data
468   case 0x19: // map16 / 16-bit bitmap
469   case 0x21: // uint16 / Unsigned 16-bit integer
470   case 0x31: // enum16 / 16-bit enumeration
471   case 0xE8: // clusterId / Cluster ID
472   case 0xE9: // attribId / Attribute ID
473   case 0xEA: // bacOID / BACnet OID
474   case 0xF1: // key128 / 128-bit security key
475   case 0xFF: // unk / Unknown
476     return 'Int16u';
477   case 0x0B: // data32 / 32-bit data
478   case 0x1B: // map32 / 32-bit bitmap
479   case 0x23: // uint32 / Unsigned 32-bit integer
480   case 0xE1: // date / Date
481   case 0xE2: // UTC / UTCTime
482     return 'Int32u';
483   case 0x0F: // data64 / 64-bit data
484   case 0x1F: // map64 / 64-bit bitmap
485   case 0x27: // uint64 / Unsigned 64-bit integer
486   case 0xF0: // EUI64 / IEEE address
487     return 'Int64u';
488   case 0x10: // bool / Boolean
489     return 'Boolean';
490   case 0x28: // int8 / Signed 8-bit integer
491     return 'Int8s';
492   case 0x29: // int16 / Signed 16-bit integer
493     return 'Int16s';
494   case 0x2B: // int32 / Signed 32-bit integer
495     return 'Int32s';
496   case 0x2F: // int64 / Signed 64-bit integer
497     return 'Int64s';
498   default:
499     error = 'Unhandled attribute type ' + attributeType;
500     throw error;
501   }
502 }
503
504 function asObjectiveCNumberType(label, type)
505 {
506   function fn(pkgId)
507   {
508     const options = { 'hash' : {} };
509     return zclHelper.asUnderlyingZclType.call(this, type, options).then(zclType => {
510       switch (zclType) {
511       case 'uint8_t':
512         return 'UnsignedChar';
513       case 'uint16_t':
514         return 'UnsignedShort';
515       case 'uint32_t':
516         return 'UnsignedLong';
517       case 'uint64_t':
518         return 'UnsignedLongLong';
519       case 'int8_t':
520         return 'Char';
521       case 'int16_t':
522         return 'Short';
523       case 'int32_t':
524         return 'Long';
525       case 'int64_t':
526         return 'LongLong';
527       default:
528         error = label + ': Unhandled underlying type ' + zclType + ' for original type ' + type;
529         throw error;
530       }
531     })
532   }
533
534   const promise = templateUtil.ensureZclPackageId(this).then(fn.bind(this)).catch(err => console.log(err));
535   return templateUtil.templatePromise(this.global, promise)
536 }
537
538 function isStrEndsWith(str, substr)
539 {
540   return str.endsWith(substr);
541 }
542
543 //
544 // Module exports
545 //
546 exports.chip_clusters                         = chip_clusters;
547 exports.chip_server_clusters                  = chip_server_clusters;
548 exports.chip_server_cluster_commands          = chip_server_cluster_commands;
549 exports.chip_server_cluster_command_arguments = chip_server_cluster_command_arguments
550 exports.asBasicType                           = ChipTypesHelper.asBasicType;
551 exports.asObjectiveCNumberType                = asObjectiveCNumberType;
552 exports.isSignedType                          = isSignedType;
553 exports.isDiscreteType                        = isDiscreteType;
554 exports.chip_server_cluster_attributes        = chip_server_cluster_attributes;
555 exports.isWritableAttribute                   = isWritableAttribute;
556 exports.isReportableAttribute                 = isReportableAttribute;
557 exports.isManufacturerSpecificCommand         = isManufacturerSpecificCommand;
558 exports.asPythonType                          = asPythonType;
559 exports.asPythonCType                         = asPythonCType;
560 exports.asCallbackAttributeType               = asCallbackAttributeType;
561 exports.hasSpecificResponse                   = hasSpecificResponse;
562 exports.isStrEndsWith                         = isStrEndsWith;