layrz-sdk 3.1.13__py3-none-any.whl → 3.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of layrz-sdk might be problematic. Click here for more details.

Files changed (75) hide show
  1. layrz_sdk/__init__.py +1 -1
  2. layrz_sdk/constants.py +5 -5
  3. layrz_sdk/entities/__init__.py +129 -129
  4. layrz_sdk/entities/asset.py +60 -60
  5. layrz_sdk/entities/asset_operation_mode.py +31 -31
  6. layrz_sdk/entities/broadcast_request.py +12 -12
  7. layrz_sdk/entities/broadcast_response.py +12 -12
  8. layrz_sdk/entities/broadcast_result.py +20 -20
  9. layrz_sdk/entities/broadcast_status.py +28 -28
  10. layrz_sdk/entities/case.py +52 -48
  11. layrz_sdk/entities/case_ignored_status.py +26 -26
  12. layrz_sdk/entities/case_status.py +23 -23
  13. layrz_sdk/entities/charts/axis_config.py +15 -15
  14. layrz_sdk/entities/charts/bar_chart.py +175 -175
  15. layrz_sdk/entities/charts/chart_alignment.py +27 -27
  16. layrz_sdk/entities/charts/chart_color.py +44 -44
  17. layrz_sdk/entities/charts/chart_configuration.py +10 -10
  18. layrz_sdk/entities/charts/chart_data_serie.py +19 -19
  19. layrz_sdk/entities/charts/chart_data_serie_type.py +28 -28
  20. layrz_sdk/entities/charts/chart_data_type.py +27 -27
  21. layrz_sdk/entities/charts/chart_render_technology.py +30 -30
  22. layrz_sdk/entities/charts/column_chart.py +201 -201
  23. layrz_sdk/entities/charts/html_chart.py +38 -38
  24. layrz_sdk/entities/charts/line_chart.py +248 -248
  25. layrz_sdk/entities/charts/map_center_type.py +22 -22
  26. layrz_sdk/entities/charts/map_chart.py +108 -108
  27. layrz_sdk/entities/charts/map_point.py +22 -22
  28. layrz_sdk/entities/charts/number_chart.py +54 -54
  29. layrz_sdk/entities/charts/pie_chart.py +131 -131
  30. layrz_sdk/entities/charts/radar_chart.py +81 -81
  31. layrz_sdk/entities/charts/radial_bar_chart.py +131 -131
  32. layrz_sdk/entities/charts/scatter_chart.py +210 -210
  33. layrz_sdk/entities/charts/scatter_serie.py +13 -13
  34. layrz_sdk/entities/charts/scatter_serie_item.py +8 -8
  35. layrz_sdk/entities/charts/table_chart.py +54 -54
  36. layrz_sdk/entities/charts/table_header.py +8 -8
  37. layrz_sdk/entities/charts/table_row.py +9 -9
  38. layrz_sdk/entities/charts/timeline_chart.py +79 -79
  39. layrz_sdk/entities/charts/timeline_serie.py +10 -10
  40. layrz_sdk/entities/charts/timeline_serie_item.py +12 -12
  41. layrz_sdk/entities/checkpoint.py +17 -17
  42. layrz_sdk/entities/comment.py +16 -16
  43. layrz_sdk/entities/custom_field.py +10 -10
  44. layrz_sdk/entities/custom_report_page.py +40 -40
  45. layrz_sdk/entities/device.py +13 -13
  46. layrz_sdk/entities/event.py +23 -23
  47. layrz_sdk/entities/geofence.py +11 -11
  48. layrz_sdk/entities/last_message.py +12 -12
  49. layrz_sdk/entities/message.py +23 -23
  50. layrz_sdk/entities/outbound_service.py +10 -10
  51. layrz_sdk/entities/position.py +116 -116
  52. layrz_sdk/entities/presence_type.py +16 -16
  53. layrz_sdk/entities/report.py +289 -289
  54. layrz_sdk/entities/report_col.py +40 -40
  55. layrz_sdk/entities/report_configuration.py +8 -8
  56. layrz_sdk/entities/report_data_type.py +28 -28
  57. layrz_sdk/entities/report_format.py +27 -27
  58. layrz_sdk/entities/report_header.py +43 -43
  59. layrz_sdk/entities/report_page.py +15 -15
  60. layrz_sdk/entities/report_row.py +28 -28
  61. layrz_sdk/entities/sensor.py +11 -11
  62. layrz_sdk/entities/text_alignment.py +26 -26
  63. layrz_sdk/entities/trigger.py +11 -11
  64. layrz_sdk/entities/user.py +10 -10
  65. layrz_sdk/entities/waypoint.py +18 -18
  66. layrz_sdk/helpers/__init__.py +5 -5
  67. layrz_sdk/helpers/color.py +44 -44
  68. layrz_sdk/lcl/__init__.py +5 -5
  69. layrz_sdk/lcl/core.py +848 -848
  70. {layrz_sdk-3.1.13.dist-info → layrz_sdk-3.1.14.dist-info}/METADATA +49 -49
  71. layrz_sdk-3.1.14.dist-info/RECORD +75 -0
  72. {layrz_sdk-3.1.13.dist-info → layrz_sdk-3.1.14.dist-info}/WHEEL +1 -1
  73. {layrz_sdk-3.1.13.dist-info → layrz_sdk-3.1.14.dist-info}/licenses/LICENSE +6 -6
  74. layrz_sdk-3.1.13.dist-info/RECORD +0 -75
  75. {layrz_sdk-3.1.13.dist-info → layrz_sdk-3.1.14.dist-info}/top_level.txt +0 -0
layrz_sdk/lcl/core.py CHANGED
@@ -1,848 +1,848 @@
1
- """Layrz Compute Language SDK"""
2
-
3
- # ruff: noqa: ANN401
4
- from typing import Any, Optional
5
-
6
- from layrz_sdk.entities.message import PayloadType
7
-
8
- PATTERN_INVALID = 'Pattern should be string, received {received}'
9
- INVALID_NUMBER_OF_PARAMS = 'Invalid number of arguments - Expected {expected} - Given {received}'
10
- DIFFERENT_TYPES_RANGES = 'Invalid data range, value: {arg1} - Minimum: {arg2} - Maximum: {arg3}'
11
- DIFFERENT_TYPES = 'Invalid data types - arg1: {arg1} - arg2: {arg2}'
12
- INVALID_ARGUMENTS = 'Invalid arguments - {e}'
13
-
14
-
15
- class LclCore:
16
- """Layrz Compute Language SDK"""
17
-
18
- def __init__(
19
- self,
20
- script: str = '',
21
- sensors: Optional[PayloadType] = None,
22
- previous_sensors: Optional[PayloadType] = None,
23
- payload: Optional[PayloadType] = None,
24
- asset_constants: Optional[dict[str, Any]] = None,
25
- custom_fields: Optional[dict[str, Any]] = None,
26
- ) -> None:
27
- """
28
- Creates a new instance of LclCore
29
-
30
- :param script: Script to be executed
31
- :type script: str
32
- :param sensors: Sensors dictionary
33
- :type sensors: Optional[PayloadType]
34
- :param previous_sensors: Previous sensors dictionary
35
- :type previous_sensors: Optional[PayloadType]
36
- :param payload: Payload dictionary
37
- :type payload: Optional[PayloadType]
38
- :param asset_constants: Asset constants dictionary
39
- :type asset_constants: Optional[dict[str, Any]]
40
- :param custom_fields: Custom fields dictionary
41
- :type custom_fields: Optional[dict[str, Any]]
42
-
43
- :return: None
44
- :rtype: None
45
- """
46
- if sensors is None:
47
- sensors = {}
48
-
49
- self._sensors = sensors
50
- if previous_sensors is None:
51
- previous_sensors = {}
52
- self._previous_sensors = previous_sensors
53
- if payload is None:
54
- payload = {}
55
- self._payload = payload
56
-
57
- if asset_constants is None:
58
- asset_constants = {}
59
- self._asset_constants = asset_constants
60
-
61
- if custom_fields is None:
62
- custom_fields = {}
63
- self._custom_fields = custom_fields
64
-
65
- self._script = script
66
-
67
- def perform(
68
- self,
69
- additional_globals: Optional[dict[str, Any]] = None,
70
- additional_locals: Optional[dict[str, Any]] = None,
71
- ) -> str:
72
- """
73
- Perform script using Layrz Compute Language
74
-
75
- :param additional_globals: Additional global variables
76
- :type additional_globals: Optional[dict[str, Any]]
77
- :param additional_locals: Additional local variables
78
- :type additional_locals: Optional[dict[str, Any]]
79
-
80
- :return: Result of the script in JSON format
81
- :rtype: str
82
- """
83
- try:
84
- local_variables = {
85
- 'payload': self._payload,
86
- 'sensors': self._sensors,
87
- 'custom_fields': self._custom_fields,
88
- 'previous_sensors': self._previous_sensors,
89
- 'asset_constants': self._asset_constants,
90
- }
91
- if additional_locals:
92
- local_variables.update(additional_locals)
93
-
94
- ## Define global variables and functions
95
- global_functions = {
96
- 'GET_PARAM': self.GET_PARAM,
97
- 'GET_SENSOR': self.GET_SENSOR,
98
- 'CONSTANT': self.CONSTANT,
99
- 'GET_CUSTOM_FIELD': self.GET_CUSTOM_FIELD,
100
- 'COMPARE': self.COMPARE,
101
- 'OR_OPERATOR': self.OR_OPERATOR,
102
- 'AND_OPERATOR': self.AND_OPERATOR,
103
- 'SUM': self.SUM,
104
- 'SUBSTRACT': self.SUBSTRACT,
105
- 'MULTIPLY': self.MULTIPLY,
106
- 'DIVIDE': self.DIVIDE,
107
- 'TO_BOOL': self.TO_BOOL,
108
- 'TO_STR': self.TO_STR,
109
- 'TO_INT': self.TO_INT,
110
- 'CEIL': self.CEIL,
111
- 'FLOOR': self.FLOOR,
112
- 'ROUND': self.ROUND,
113
- 'SQRT': self.SQRT,
114
- 'CONCAT': self.CONCAT,
115
- 'NOW': self.NOW,
116
- 'RANDOM': self.RANDOM,
117
- 'RANDOM_INT': self.RANDOM_INT,
118
- 'GREATER_THAN_OR_EQUALS_TO': self.GREATER_THAN_OR_EQUALS_TO,
119
- 'GREATER_THAN': self.GREATER_THAN,
120
- 'LESS_THAN_OR_EQUALS_TO': self.LESS_THAN_OR_EQUALS_TO,
121
- 'LESS_THAN': self.LESS_THAN,
122
- 'DIFFERENT': self.DIFFERENT,
123
- 'HEX_TO_STR': self.HEX_TO_STR,
124
- 'STR_TO_HEX': self.STR_TO_HEX,
125
- 'HEX_TO_INT': self.HEX_TO_INT,
126
- 'INT_TO_HEX': self.INT_TO_HEX,
127
- 'TO_FLOAT': self.TO_FLOAT,
128
- 'GET_DISTANCE_TRAVELED': self.GET_DISTANCE_TRAVELED,
129
- 'GET_PREVIOUS_SENSOR': self.GET_PREVIOUS_SENSOR,
130
- 'IS_PARAMETER_PRESENT': self.IS_PARAMETER_PRESENT,
131
- 'IS_SENSOR_PRESENT': self.IS_SENSOR_PRESENT,
132
- 'INSIDE_RANGE': self.INSIDE_RANGE,
133
- 'OUTSIDE_RANGE': self.OUTSIDE_RANGE,
134
- 'GET_TIME_DIFFERENCE': self.GET_TIME_DIFFERENCE,
135
- 'IF': self.IF,
136
- 'REGEX': self.REGEX,
137
- 'IS_NONE': self.IS_NONE,
138
- 'NOT': self.NOT,
139
- 'CONTAINS': self.CONTAINS,
140
- 'STARTS_WITH': self.STARTS_WITH,
141
- 'ENDS_WITH': self.ENDS_WITH,
142
- 'PRIMARY_DEVICE': self.PRIMARY_DEVICE,
143
- 'SUBSTRING': self.SUBSTRING,
144
- 'UNIX_TO_STR': self.UNIX_TO_STR,
145
- 'VERSION': self.VERSION,
146
- }
147
-
148
- if additional_globals:
149
- global_functions.update(additional_globals)
150
-
151
- import json
152
-
153
- result = json.dumps(eval(self._script, global_functions, local_variables))
154
-
155
- return result
156
- except Exception as err:
157
- import json
158
-
159
- return json.dumps(INVALID_ARGUMENTS.format(e=err))
160
-
161
- def _standarize_datatypes(self, args: list[Any]) -> list[Any]:
162
- """Standarize data types"""
163
- result_args = []
164
-
165
- for arg in args:
166
- if isinstance(arg, (float, int)):
167
- result_args.append(float(arg))
168
- else:
169
- result_args.append(arg)
170
-
171
- return result_args
172
-
173
- def GET_PARAM(self, *args: list[Any]) -> Any:
174
- """GET_PARAM Function"""
175
- if len(args) > 2:
176
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
177
-
178
- if args[0] is None:
179
- return None
180
-
181
- if len(args) > 1:
182
- return self._payload.get(args[0], args[1]) # type: ignore
183
- return self._payload.get(args[0], None) # type: ignore
184
-
185
- def GET_DISTANCE_TRAVELED(self, *args: list[Any]) -> str | float:
186
- """GET_DISTANCE_TRAVELED Function"""
187
- if len(args) > 0:
188
- return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
189
- return self._asset_constants.get('distanceTraveled', 0) # type: ignore
190
-
191
- def GET_PREVIOUS_SENSOR(self, *args: list[Any]) -> Any:
192
- """GET_PREVIOUS_SENSOR Function"""
193
- if len(args) < 1:
194
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
195
-
196
- if len(args) > 2:
197
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
198
-
199
- if args[0] is None:
200
- return None
201
-
202
- if len(args) > 1:
203
- return self._previous_sensors.get(args[0], args[1]) # type: ignore
204
- return self._previous_sensors.get(args[0], None) # type: ignore
205
-
206
- def GET_SENSOR(self, *args: list[Any]) -> Any:
207
- """GET_SENSOR Function"""
208
- if len(args) < 1:
209
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
210
-
211
- if len(args) > 2:
212
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
213
-
214
- if args[0] is None:
215
- return None
216
-
217
- if len(args) > 1:
218
- return self._sensors.get(args[0], args[1]) # type: ignore
219
- return self._sensors.get(args[0], None) # type: ignore
220
-
221
- def CONSTANT(self, *args: list[Any]) -> Any:
222
- """CONSTANT Function"""
223
- if len(args) > 1:
224
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
225
- return args[0]
226
-
227
- def GET_CUSTOM_FIELD(self, *args: list[Any]) -> str:
228
- """GET_CUSTOM_FIELD Function"""
229
- if len(args) > 2:
230
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
231
-
232
- if args[0] is None:
233
- return None
234
-
235
- if len(args) > 1:
236
- return self._custom_fields.get(args[0], args[1]) # type: ignore
237
-
238
- return self._custom_fields.get(args[0], '') # type: ignore
239
-
240
- def COMPARE(self, *args: list[Any]) -> str | None | bool:
241
- """COMPARE Function"""
242
- if len(args) != 2:
243
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
244
-
245
- if args[0] is None or args[1] is None:
246
- return None
247
-
248
- args = self._standarize_datatypes(args) # type: ignore
249
-
250
- if not isinstance(args[0], type(args[1])):
251
- return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
252
- return args[0] == args[1]
253
-
254
- def OR_OPERATOR(self, *args: list[Any]) -> bool:
255
- """OR_OPERATOR Function"""
256
- result = False
257
-
258
- for val in args:
259
- if val is None:
260
- return None
261
- if isinstance(val, bool):
262
- result = result or val
263
-
264
- return result
265
-
266
- def AND_OPERATOR(self, *args: list[Any]) -> bool:
267
- """AND_OPERATOR Function"""
268
- result = False
269
- is_first = True
270
-
271
- for val in args:
272
- if val is None:
273
- return None
274
-
275
- if is_first:
276
- is_first = False
277
- result = val # type: ignore
278
- elif isinstance(val, bool):
279
- result = result and val
280
-
281
- return result
282
-
283
- def SUM(self, *args: list[Any]) -> float:
284
- """SUM Function"""
285
- result = 0
286
-
287
- for num in args:
288
- if num is None:
289
- return None
290
-
291
- try:
292
- result += float(num) # type: ignore
293
- except Exception:
294
- pass
295
-
296
- return result
297
-
298
- def SUBSTRACT(self, *args: list[Any]) -> float:
299
- """SUBSTRACT Function"""
300
- result = 0
301
- is_first = True
302
-
303
- for num in args:
304
- if num is None:
305
- return None
306
-
307
- try:
308
- if is_first:
309
- result = float(num) # type: ignore
310
- is_first = False
311
- else:
312
- result -= float(num) # type: ignore
313
- except Exception:
314
- pass
315
-
316
- return result
317
-
318
- def MULTIPLY(self, *args: list[Any]) -> float:
319
- """MULTIPLY Function"""
320
- result = 0
321
- is_first = True
322
-
323
- for num in args:
324
- if num is None:
325
- return None
326
-
327
- try:
328
- if is_first:
329
- is_first = False
330
- result = float(num) # type: ignore
331
- else:
332
- result *= float(num) # type: ignore
333
- except Exception:
334
- pass
335
-
336
- return result
337
-
338
- def DIVIDE(self, *args: list[Any]) -> float:
339
- """DIVIDE Function"""
340
- result = 0
341
- is_first = True
342
-
343
- for num in args:
344
- if num is None:
345
- return None
346
-
347
- try:
348
- if is_first:
349
- is_first = False
350
- result = float(num) # type: ignore
351
- else:
352
- result /= float(num) # type: ignore
353
- except Exception:
354
- pass
355
-
356
- return result
357
-
358
- def TO_BOOL(self, *args: list[Any]) -> str | None | bool:
359
- """TO_BOOL Function"""
360
- if len(args) > 1:
361
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
362
-
363
- if args[0] is None:
364
- return None
365
-
366
- return bool(args[0])
367
-
368
- def TO_STR(self, *args: list[Any]) -> str | None:
369
- """TO_STR Function"""
370
- if len(args) > 1:
371
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
372
-
373
- if args[0] is None:
374
- return None
375
-
376
- return str(args[0])
377
-
378
- def TO_INT(self, *args: list[Any]) -> str | None | int:
379
- """TO_INT Function"""
380
- if len(args) > 1:
381
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
382
-
383
- if args[0] is None:
384
- return None
385
-
386
- return int(args[0]) # type: ignore
387
-
388
- def CEIL(self, *args: list[Any]) -> str | None | int:
389
- """CEIL Function"""
390
- if len(args) > 1:
391
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
392
-
393
- if args[0] is None:
394
- return None
395
-
396
- if not isinstance(args[0], (int, float)):
397
- return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
398
-
399
- import math
400
-
401
- return math.ceil(args[0])
402
-
403
- def FLOOR(self, *args: list[Any]) -> str | None | int:
404
- """FLOOR Function"""
405
- if len(args) > 1:
406
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
407
-
408
- if args[0] is None:
409
- return None
410
-
411
- if not isinstance(args[0], (int, float)):
412
- return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
413
-
414
- import math
415
-
416
- return math.floor(args[0])
417
-
418
- def ROUND(self, *args: list[Any]) -> str | None | int:
419
- """ROUND Function"""
420
- if len(args) > 1:
421
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
422
-
423
- if args[0] is None:
424
- return None
425
-
426
- if not isinstance(args[0], (int, float)):
427
- return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
428
-
429
- return round(args[0])
430
-
431
- def SQRT(self, *args: list[Any]) -> str | None | float:
432
- """SQRT Function"""
433
- if len(args) > 1:
434
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
435
-
436
- if args[0] is None:
437
- return None
438
-
439
- import math
440
-
441
- return math.sqrt(args[0]) # type: ignore
442
-
443
- def CONCAT(self, *args: list[Any]) -> str | None:
444
- """CONCAT Function"""
445
- for val in args:
446
- if val is None:
447
- return None
448
-
449
- return ''.join([str(val) for val in args])
450
-
451
- def RANDOM(self, *args: list[Any]) -> float | str:
452
- """RANDOM Function"""
453
- if len(args) > 2:
454
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
455
- if len(args) < 2:
456
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
457
-
458
- if args[0] is None or args[1] is None:
459
- return None
460
-
461
- import random
462
-
463
- return random.random() * (float(args[1]) - float(args[0])) + float(args[0]) # type: ignore
464
-
465
- def RANDOM_INT(self, *args: list[Any]) -> int | str:
466
- """RANDOM_INT Function"""
467
- if len(args) != 2:
468
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
469
-
470
- if args[0] is None or args[1] is None:
471
- return None
472
-
473
- import random
474
-
475
- return random.randint(int(args[0]), int(args[1])) # type: ignore
476
-
477
- def GREATER_THAN_OR_EQUALS_TO(self, *args: list[Any]) -> str | None | bool:
478
- """GREATER_THAN_OR_EQUALS_TO Function"""
479
- if len(args) > 2:
480
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
481
- if len(args) < 2:
482
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
483
-
484
- if args[0] is None or args[1] is None:
485
- return None
486
-
487
- args = self._standarize_datatypes(args) # type: ignore
488
-
489
- if not isinstance(args[0], type(args[1])):
490
- return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
491
- return args[0] >= args[1]
492
-
493
- def GREATER_THAN(self, *args: list[Any]) -> str | None | bool:
494
- """GREATER_THAN Function"""
495
- if len(args) > 2:
496
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
497
- if len(args) < 2:
498
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
499
-
500
- if args[0] is None or args[1] is None:
501
- return None
502
-
503
- args = self._standarize_datatypes(args) # type: ignore
504
-
505
- if not isinstance(args[0], type(args[1])):
506
- return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
507
- return args[0] > args[1]
508
-
509
- def LESS_THAN_OR_EQUALS_TO(self, *args: list[Any]) -> str | None | bool:
510
- """LESS_THAN_OR_EQUALS_TO Function"""
511
- if len(args) > 2:
512
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
513
- if len(args) < 2:
514
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
515
-
516
- if args[0] is None or args[1] is None:
517
- return None
518
-
519
- args = self._standarize_datatypes(args) # type: ignore
520
-
521
- if not isinstance(args[0], type(args[1])):
522
- return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
523
- return args[0] <= args[1]
524
-
525
- def LESS_THAN(self, *args: list[Any]) -> str | None | bool:
526
- """LESS_THAN Function"""
527
- if len(args) > 2:
528
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
529
- if len(args) < 2:
530
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
531
-
532
- if args[0] is None or args[1] is None:
533
- return None
534
-
535
- args = self._standarize_datatypes(args) # type: ignore
536
-
537
- if not isinstance(args[0], type(args[1])):
538
- return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
539
- return args[0] < args[1]
540
-
541
- def DIFFERENT(self, *args: list[Any]) -> str | None | bool:
542
- """DIFFERENT Function"""
543
- if len(args) > 2:
544
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
545
- if len(args) < 2:
546
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
547
-
548
- if args[0] is None or args[1] is None:
549
- return None
550
-
551
- args = self._standarize_datatypes(args) # type: ignore
552
-
553
- if not isinstance(args[0], type(args[1])):
554
- return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
555
- return args[0] != args[1]
556
-
557
- def HEX_TO_STR(self, *args: list[Any]) -> str | None:
558
- """HEX_TO_STR Function"""
559
- if len(args) > 1:
560
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
561
-
562
- if args[0] is None:
563
- return None
564
-
565
- hexa = args[0]
566
- if hexa.startswith('0x'): # type: ignore
567
- hexa = hexa[2:]
568
-
569
- try:
570
- byte_array = bytes.fromhex(hexa) # type: ignore
571
- return byte_array.decode('ASCII')
572
- except Exception:
573
- return 'Invalid hex string'
574
-
575
- def STR_TO_HEX(self, *args: list[Any]) -> str | None:
576
- """STR_TO_HEX Function"""
577
- if len(args) > 1:
578
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
579
-
580
- if args[0] is None:
581
- return None
582
-
583
- return str(args[0]).encode('ASCII').hex()
584
-
585
- def HEX_TO_INT(self, *args: list[Any]) -> str | None | int:
586
- """HEX_TO_INT Function"""
587
- if len(args) > 1:
588
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
589
-
590
- if args[0] is None:
591
- return None
592
-
593
- try:
594
- return int(int(args[0], 16)) # type: ignore
595
- except Exception:
596
- return 'Invalid hex string'
597
-
598
- def INT_TO_HEX(self, *args: list[Any]) -> str | None:
599
- """INT_TO_HEX Function"""
600
- if len(args) > 1:
601
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
602
-
603
- if args[0] is None:
604
- return None
605
-
606
- try:
607
- return hex(int(args[0]))[2:] # type: ignore
608
- except Exception:
609
- return 'Invalid int value'
610
-
611
- def TO_FLOAT(self, *args: list[Any]) -> str | None | float:
612
- """TO_FLOAT Function"""
613
- if len(args) > 1:
614
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
615
-
616
- if args[0] is None:
617
- return None
618
-
619
- try:
620
- return float(args[0]) # type: ignore
621
- except Exception:
622
- return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
623
-
624
- def IS_PARAMETER_PRESENT(self, *args: list[Any]) -> str | bool:
625
- """IS_PARAMETER_PRESENT Function"""
626
- if len(args) > 1:
627
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
628
-
629
- if args[0] is None:
630
- return None
631
-
632
- return args[0] in self._payload # type: ignore
633
-
634
- def IS_SENSOR_PRESENT(self, *args: list[Any]) -> str | bool:
635
- """IS_SENSOR_PRESENT Function"""
636
- if len(args) > 1:
637
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
638
-
639
- if args[0] is None:
640
- return None
641
-
642
- return args[0] in self._sensors # type: ignore
643
-
644
- def INSIDE_RANGE(self, *args: list[Any]) -> str | None | bool:
645
- """INSIDE_RANGE Function"""
646
- if len(args) != 3:
647
- return INVALID_NUMBER_OF_PARAMS.format(expected=3, received=len(args))
648
-
649
- if args[0] is None or args[1] is None or args[2] is None:
650
- return None
651
-
652
- args = self._standarize_datatypes(args) # type: ignore
653
-
654
- if not isinstance(args[0], type(args[1])):
655
- return DIFFERENT_TYPES_RANGES.format(
656
- arg1=type(args[0]).__name__,
657
- arg2=type(args[1]).__name__,
658
- arg3=type(args[2]).__name__,
659
- )
660
-
661
- return args[1] <= args[0] <= args[2]
662
-
663
- def OUTSIDE_RANGE(self, *args: list[Any]) -> str | None | bool:
664
- """OUTSIDE_RANGE Function"""
665
- if len(args) != 3:
666
- return INVALID_NUMBER_OF_PARAMS.format(expected=3, received=len(args))
667
-
668
- if args[0] is None or args[1] is None or args[2] is None:
669
- return None
670
-
671
- args = self._standarize_datatypes(args) # type: ignore
672
-
673
- if not isinstance(args[0], type(args[1])):
674
- return DIFFERENT_TYPES_RANGES.format(
675
- arg1=type(args[0]).__name__,
676
- arg2=type(args[1]).__name__,
677
- arg3=type(args[2]).__name__,
678
- )
679
-
680
- return not args[1] <= args[0] <= args[2]
681
-
682
- def GET_TIME_DIFFERENCE(self, *args: list[Any]) -> str | float:
683
- """GET_TIME_DIFFERENCE Function"""
684
- if len(args) > 0:
685
- return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
686
-
687
- return self._asset_constants.get('timeElapsed', 0) # type: ignore
688
-
689
- def IF(self, *args: list[Any]) -> Any:
690
- """IF Function"""
691
- if len(args) != 3:
692
- return INVALID_NUMBER_OF_PARAMS.format(expected=3, received=len(args))
693
-
694
- if args[0] is None:
695
- return None
696
-
697
- return args[1] if args[0] else args[2]
698
-
699
- def NOW(self, *args: list[Any]) -> float:
700
- """NOW Function"""
701
- import zoneinfo
702
- from datetime import datetime
703
-
704
- return datetime.now(tz=zoneinfo.ZoneInfo('UTC')).timestamp()
705
-
706
- def REGEX(self, *args: list[Any]) -> str | None | bool:
707
- """REGEX Function"""
708
- if len(args) != 2:
709
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
710
-
711
- if args[0] is None or args[1] is None:
712
- return None
713
-
714
- if not isinstance(args[0], str):
715
- return PATTERN_INVALID.format(received=type(args[0]))
716
-
717
- import re
718
-
719
- pattern = re.compile(args[1])
720
- return bool(pattern.match(args[0]))
721
-
722
- def IS_NONE(self, *args: list[Any]) -> str | bool:
723
- """IS_NONE Function"""
724
- if len(args) != 1:
725
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
726
-
727
- return args[0] is None
728
-
729
- def NOT(self, *args: list[Any]) -> str | bool:
730
- """NOT Function"""
731
- if len(args) != 1:
732
- return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
733
-
734
- if args[0] is None:
735
- return None
736
-
737
- return not args[0]
738
-
739
- def CONTAINS(self, *args: list[Any]) -> str | bool:
740
- """CONTAINS function"""
741
- if len(args) != 2:
742
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
743
-
744
- if args[0] is None or args[1] is None:
745
- return None
746
-
747
- return str(args[0]) in str(args[1])
748
-
749
- def STARTS_WITH(self, *args: list[Any]) -> str | bool:
750
- """STARTS_WITH function"""
751
- if len(args) != 2:
752
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
753
-
754
- if args[0] is None or args[1] is None:
755
- return None
756
-
757
- return str(args[1]).startswith(str(args[0]))
758
-
759
- def ENDS_WITH(self, *args: list[Any]) -> str | bool:
760
- """ENDS_WITH function"""
761
- if len(args) != 2:
762
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
763
-
764
- if args[0] is None or args[1] is None:
765
- return None
766
-
767
- return str(args[1]).endswith(str(args[0]))
768
-
769
- def PRIMARY_DEVICE(self, *args: list[Any]) -> str:
770
- """PRIMARY_DEVICE function"""
771
- if len(args) > 0:
772
- return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
773
-
774
- return self._asset_constants.get('primaryDevice', None) # type: ignore
775
-
776
- def SUBSTRING(self, *args: list[Any]) -> str:
777
- """Get a substring from string (args[0])"""
778
- if len(args) < 2:
779
- return INVALID_NUMBER_OF_PARAMS.format(
780
- expected=2,
781
- received=len(args),
782
- )
783
-
784
- if len(args) > 3:
785
- return INVALID_NUMBER_OF_PARAMS.format(
786
- expected=3,
787
- received=len(args),
788
- )
789
-
790
- if args[0] is None:
791
- return None
792
-
793
- if not isinstance(args[0], str):
794
- return DIFFERENT_TYPES.format(arg1='str', arg2=type(args[0]).__name__)
795
-
796
- if args[1] is None:
797
- return None
798
-
799
- if not isinstance(args[1], int):
800
- return DIFFERENT_TYPES.format(arg1='int', arg2=type(args[1]).__name__)
801
-
802
- if len(args) == 3:
803
- if args[2] is None:
804
- return None
805
-
806
- if not isinstance(args[2], int):
807
- return DIFFERENT_TYPES.format(arg1='str', arg2=type(args[2]).__name__)
808
-
809
- return args[0][args[1] : args[2]]
810
- return args[0][args[1] :]
811
-
812
- def UNIX_TO_STR(self, *args: list[Any]) -> str:
813
- """Convert UNIX timestamp date (args[0]) to format (args[1]) string"""
814
- if len(args) < 2:
815
- return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
816
-
817
- if args[0] is None:
818
- return None
819
-
820
- if args[1] is None:
821
- return None
822
-
823
- import zoneinfo
824
- from datetime import datetime
825
-
826
- tz = zoneinfo.ZoneInfo('UTC')
827
-
828
- if len(args) > 2:
829
- if args[2] is None:
830
- return None
831
-
832
- try:
833
- tz = zoneinfo.ZoneInfo(args[2]) # type: ignore
834
- except zoneinfo.ZoneInfoNotFoundError:
835
- tz = zoneinfo.ZoneInfo('UTC')
836
-
837
- return datetime.fromtimestamp(int(args[0]), tz=zoneinfo.ZoneInfo('UTC')).astimezone(tz).strftime(args[1]) # type: ignore
838
-
839
- def VERSION(self, *args: list[Any]) -> str:
840
- """VERSION function"""
841
- if len(args) > 0:
842
- return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
843
-
844
- import importlib.metadata
845
-
846
- version = importlib.metadata.version('layrz_sdk')
847
-
848
- return f'LCL {version} - Layrz SDK Runtime'
1
+ """Layrz Compute Language SDK"""
2
+
3
+ # ruff: noqa: ANN401
4
+ from typing import Any, Optional
5
+
6
+ from layrz_sdk.entities.message import PayloadType
7
+
8
+ PATTERN_INVALID = 'Pattern should be string, received {received}'
9
+ INVALID_NUMBER_OF_PARAMS = 'Invalid number of arguments - Expected {expected} - Given {received}'
10
+ DIFFERENT_TYPES_RANGES = 'Invalid data range, value: {arg1} - Minimum: {arg2} - Maximum: {arg3}'
11
+ DIFFERENT_TYPES = 'Invalid data types - arg1: {arg1} - arg2: {arg2}'
12
+ INVALID_ARGUMENTS = 'Invalid arguments - {e}'
13
+
14
+
15
+ class LclCore:
16
+ """Layrz Compute Language SDK"""
17
+
18
+ def __init__(
19
+ self,
20
+ script: str = '',
21
+ sensors: Optional[PayloadType] = None,
22
+ previous_sensors: Optional[PayloadType] = None,
23
+ payload: Optional[PayloadType] = None,
24
+ asset_constants: Optional[dict[str, Any]] = None,
25
+ custom_fields: Optional[dict[str, Any]] = None,
26
+ ) -> None:
27
+ """
28
+ Creates a new instance of LclCore
29
+
30
+ :param script: Script to be executed
31
+ :type script: str
32
+ :param sensors: Sensors dictionary
33
+ :type sensors: Optional[PayloadType]
34
+ :param previous_sensors: Previous sensors dictionary
35
+ :type previous_sensors: Optional[PayloadType]
36
+ :param payload: Payload dictionary
37
+ :type payload: Optional[PayloadType]
38
+ :param asset_constants: Asset constants dictionary
39
+ :type asset_constants: Optional[dict[str, Any]]
40
+ :param custom_fields: Custom fields dictionary
41
+ :type custom_fields: Optional[dict[str, Any]]
42
+
43
+ :return: None
44
+ :rtype: None
45
+ """
46
+ if sensors is None:
47
+ sensors = {}
48
+
49
+ self._sensors = sensors
50
+ if previous_sensors is None:
51
+ previous_sensors = {}
52
+ self._previous_sensors = previous_sensors
53
+ if payload is None:
54
+ payload = {}
55
+ self._payload = payload
56
+
57
+ if asset_constants is None:
58
+ asset_constants = {}
59
+ self._asset_constants = asset_constants
60
+
61
+ if custom_fields is None:
62
+ custom_fields = {}
63
+ self._custom_fields = custom_fields
64
+
65
+ self._script = script
66
+
67
+ def perform(
68
+ self,
69
+ additional_globals: Optional[dict[str, Any]] = None,
70
+ additional_locals: Optional[dict[str, Any]] = None,
71
+ ) -> str:
72
+ """
73
+ Perform script using Layrz Compute Language
74
+
75
+ :param additional_globals: Additional global variables
76
+ :type additional_globals: Optional[dict[str, Any]]
77
+ :param additional_locals: Additional local variables
78
+ :type additional_locals: Optional[dict[str, Any]]
79
+
80
+ :return: Result of the script in JSON format
81
+ :rtype: str
82
+ """
83
+ try:
84
+ local_variables = {
85
+ 'payload': self._payload,
86
+ 'sensors': self._sensors,
87
+ 'custom_fields': self._custom_fields,
88
+ 'previous_sensors': self._previous_sensors,
89
+ 'asset_constants': self._asset_constants,
90
+ }
91
+ if additional_locals:
92
+ local_variables.update(additional_locals)
93
+
94
+ ## Define global variables and functions
95
+ global_functions = {
96
+ 'GET_PARAM': self.GET_PARAM,
97
+ 'GET_SENSOR': self.GET_SENSOR,
98
+ 'CONSTANT': self.CONSTANT,
99
+ 'GET_CUSTOM_FIELD': self.GET_CUSTOM_FIELD,
100
+ 'COMPARE': self.COMPARE,
101
+ 'OR_OPERATOR': self.OR_OPERATOR,
102
+ 'AND_OPERATOR': self.AND_OPERATOR,
103
+ 'SUM': self.SUM,
104
+ 'SUBSTRACT': self.SUBSTRACT,
105
+ 'MULTIPLY': self.MULTIPLY,
106
+ 'DIVIDE': self.DIVIDE,
107
+ 'TO_BOOL': self.TO_BOOL,
108
+ 'TO_STR': self.TO_STR,
109
+ 'TO_INT': self.TO_INT,
110
+ 'CEIL': self.CEIL,
111
+ 'FLOOR': self.FLOOR,
112
+ 'ROUND': self.ROUND,
113
+ 'SQRT': self.SQRT,
114
+ 'CONCAT': self.CONCAT,
115
+ 'NOW': self.NOW,
116
+ 'RANDOM': self.RANDOM,
117
+ 'RANDOM_INT': self.RANDOM_INT,
118
+ 'GREATER_THAN_OR_EQUALS_TO': self.GREATER_THAN_OR_EQUALS_TO,
119
+ 'GREATER_THAN': self.GREATER_THAN,
120
+ 'LESS_THAN_OR_EQUALS_TO': self.LESS_THAN_OR_EQUALS_TO,
121
+ 'LESS_THAN': self.LESS_THAN,
122
+ 'DIFFERENT': self.DIFFERENT,
123
+ 'HEX_TO_STR': self.HEX_TO_STR,
124
+ 'STR_TO_HEX': self.STR_TO_HEX,
125
+ 'HEX_TO_INT': self.HEX_TO_INT,
126
+ 'INT_TO_HEX': self.INT_TO_HEX,
127
+ 'TO_FLOAT': self.TO_FLOAT,
128
+ 'GET_DISTANCE_TRAVELED': self.GET_DISTANCE_TRAVELED,
129
+ 'GET_PREVIOUS_SENSOR': self.GET_PREVIOUS_SENSOR,
130
+ 'IS_PARAMETER_PRESENT': self.IS_PARAMETER_PRESENT,
131
+ 'IS_SENSOR_PRESENT': self.IS_SENSOR_PRESENT,
132
+ 'INSIDE_RANGE': self.INSIDE_RANGE,
133
+ 'OUTSIDE_RANGE': self.OUTSIDE_RANGE,
134
+ 'GET_TIME_DIFFERENCE': self.GET_TIME_DIFFERENCE,
135
+ 'IF': self.IF,
136
+ 'REGEX': self.REGEX,
137
+ 'IS_NONE': self.IS_NONE,
138
+ 'NOT': self.NOT,
139
+ 'CONTAINS': self.CONTAINS,
140
+ 'STARTS_WITH': self.STARTS_WITH,
141
+ 'ENDS_WITH': self.ENDS_WITH,
142
+ 'PRIMARY_DEVICE': self.PRIMARY_DEVICE,
143
+ 'SUBSTRING': self.SUBSTRING,
144
+ 'UNIX_TO_STR': self.UNIX_TO_STR,
145
+ 'VERSION': self.VERSION,
146
+ }
147
+
148
+ if additional_globals:
149
+ global_functions.update(additional_globals)
150
+
151
+ import json
152
+
153
+ result = json.dumps(eval(self._script, global_functions, local_variables))
154
+
155
+ return result
156
+ except Exception as err:
157
+ import json
158
+
159
+ return json.dumps(INVALID_ARGUMENTS.format(e=err))
160
+
161
+ def _standarize_datatypes(self, args: list[Any]) -> list[Any]:
162
+ """Standarize data types"""
163
+ result_args = []
164
+
165
+ for arg in args:
166
+ if isinstance(arg, (float, int)):
167
+ result_args.append(float(arg))
168
+ else:
169
+ result_args.append(arg)
170
+
171
+ return result_args
172
+
173
+ def GET_PARAM(self, *args: list[Any]) -> Any:
174
+ """GET_PARAM Function"""
175
+ if len(args) > 2:
176
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
177
+
178
+ if args[0] is None:
179
+ return None
180
+
181
+ if len(args) > 1:
182
+ return self._payload.get(args[0], args[1]) # type: ignore
183
+ return self._payload.get(args[0], None) # type: ignore
184
+
185
+ def GET_DISTANCE_TRAVELED(self, *args: list[Any]) -> str | float:
186
+ """GET_DISTANCE_TRAVELED Function"""
187
+ if len(args) > 0:
188
+ return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
189
+ return self._asset_constants.get('distanceTraveled', 0) # type: ignore
190
+
191
+ def GET_PREVIOUS_SENSOR(self, *args: list[Any]) -> Any:
192
+ """GET_PREVIOUS_SENSOR Function"""
193
+ if len(args) < 1:
194
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
195
+
196
+ if len(args) > 2:
197
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
198
+
199
+ if args[0] is None:
200
+ return None
201
+
202
+ if len(args) > 1:
203
+ return self._previous_sensors.get(args[0], args[1]) # type: ignore
204
+ return self._previous_sensors.get(args[0], None) # type: ignore
205
+
206
+ def GET_SENSOR(self, *args: list[Any]) -> Any:
207
+ """GET_SENSOR Function"""
208
+ if len(args) < 1:
209
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
210
+
211
+ if len(args) > 2:
212
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
213
+
214
+ if args[0] is None:
215
+ return None
216
+
217
+ if len(args) > 1:
218
+ return self._sensors.get(args[0], args[1]) # type: ignore
219
+ return self._sensors.get(args[0], None) # type: ignore
220
+
221
+ def CONSTANT(self, *args: list[Any]) -> Any:
222
+ """CONSTANT Function"""
223
+ if len(args) > 1:
224
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
225
+ return args[0]
226
+
227
+ def GET_CUSTOM_FIELD(self, *args: list[Any]) -> str:
228
+ """GET_CUSTOM_FIELD Function"""
229
+ if len(args) > 2:
230
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
231
+
232
+ if args[0] is None:
233
+ return None
234
+
235
+ if len(args) > 1:
236
+ return self._custom_fields.get(args[0], args[1]) # type: ignore
237
+
238
+ return self._custom_fields.get(args[0], '') # type: ignore
239
+
240
+ def COMPARE(self, *args: list[Any]) -> str | None | bool:
241
+ """COMPARE Function"""
242
+ if len(args) != 2:
243
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
244
+
245
+ if args[0] is None or args[1] is None:
246
+ return None
247
+
248
+ args = self._standarize_datatypes(args) # type: ignore
249
+
250
+ if not isinstance(args[0], type(args[1])):
251
+ return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
252
+ return args[0] == args[1]
253
+
254
+ def OR_OPERATOR(self, *args: list[Any]) -> bool:
255
+ """OR_OPERATOR Function"""
256
+ result = False
257
+
258
+ for val in args:
259
+ if val is None:
260
+ return None
261
+ if isinstance(val, bool):
262
+ result = result or val
263
+
264
+ return result
265
+
266
+ def AND_OPERATOR(self, *args: list[Any]) -> bool:
267
+ """AND_OPERATOR Function"""
268
+ result = False
269
+ is_first = True
270
+
271
+ for val in args:
272
+ if val is None:
273
+ return None
274
+
275
+ if is_first:
276
+ is_first = False
277
+ result = val # type: ignore
278
+ elif isinstance(val, bool):
279
+ result = result and val
280
+
281
+ return result
282
+
283
+ def SUM(self, *args: list[Any]) -> float:
284
+ """SUM Function"""
285
+ result = 0
286
+
287
+ for num in args:
288
+ if num is None:
289
+ return None
290
+
291
+ try:
292
+ result += float(num) # type: ignore
293
+ except Exception:
294
+ pass
295
+
296
+ return result
297
+
298
+ def SUBSTRACT(self, *args: list[Any]) -> float:
299
+ """SUBSTRACT Function"""
300
+ result = 0
301
+ is_first = True
302
+
303
+ for num in args:
304
+ if num is None:
305
+ return None
306
+
307
+ try:
308
+ if is_first:
309
+ result = float(num) # type: ignore
310
+ is_first = False
311
+ else:
312
+ result -= float(num) # type: ignore
313
+ except Exception:
314
+ pass
315
+
316
+ return result
317
+
318
+ def MULTIPLY(self, *args: list[Any]) -> float:
319
+ """MULTIPLY Function"""
320
+ result = 0
321
+ is_first = True
322
+
323
+ for num in args:
324
+ if num is None:
325
+ return None
326
+
327
+ try:
328
+ if is_first:
329
+ is_first = False
330
+ result = float(num) # type: ignore
331
+ else:
332
+ result *= float(num) # type: ignore
333
+ except Exception:
334
+ pass
335
+
336
+ return result
337
+
338
+ def DIVIDE(self, *args: list[Any]) -> float:
339
+ """DIVIDE Function"""
340
+ result = 0
341
+ is_first = True
342
+
343
+ for num in args:
344
+ if num is None:
345
+ return None
346
+
347
+ try:
348
+ if is_first:
349
+ is_first = False
350
+ result = float(num) # type: ignore
351
+ else:
352
+ result /= float(num) # type: ignore
353
+ except Exception:
354
+ pass
355
+
356
+ return result
357
+
358
+ def TO_BOOL(self, *args: list[Any]) -> str | None | bool:
359
+ """TO_BOOL Function"""
360
+ if len(args) > 1:
361
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
362
+
363
+ if args[0] is None:
364
+ return None
365
+
366
+ return bool(args[0])
367
+
368
+ def TO_STR(self, *args: list[Any]) -> str | None:
369
+ """TO_STR Function"""
370
+ if len(args) > 1:
371
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
372
+
373
+ if args[0] is None:
374
+ return None
375
+
376
+ return str(args[0])
377
+
378
+ def TO_INT(self, *args: list[Any]) -> str | None | int:
379
+ """TO_INT Function"""
380
+ if len(args) > 1:
381
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
382
+
383
+ if args[0] is None:
384
+ return None
385
+
386
+ return int(args[0]) # type: ignore
387
+
388
+ def CEIL(self, *args: list[Any]) -> str | None | int:
389
+ """CEIL Function"""
390
+ if len(args) > 1:
391
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
392
+
393
+ if args[0] is None:
394
+ return None
395
+
396
+ if not isinstance(args[0], (int, float)):
397
+ return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
398
+
399
+ import math
400
+
401
+ return math.ceil(args[0])
402
+
403
+ def FLOOR(self, *args: list[Any]) -> str | None | int:
404
+ """FLOOR Function"""
405
+ if len(args) > 1:
406
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
407
+
408
+ if args[0] is None:
409
+ return None
410
+
411
+ if not isinstance(args[0], (int, float)):
412
+ return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
413
+
414
+ import math
415
+
416
+ return math.floor(args[0])
417
+
418
+ def ROUND(self, *args: list[Any]) -> str | None | int:
419
+ """ROUND Function"""
420
+ if len(args) > 1:
421
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
422
+
423
+ if args[0] is None:
424
+ return None
425
+
426
+ if not isinstance(args[0], (int, float)):
427
+ return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
428
+
429
+ return round(args[0])
430
+
431
+ def SQRT(self, *args: list[Any]) -> str | None | float:
432
+ """SQRT Function"""
433
+ if len(args) > 1:
434
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
435
+
436
+ if args[0] is None:
437
+ return None
438
+
439
+ import math
440
+
441
+ return math.sqrt(args[0]) # type: ignore
442
+
443
+ def CONCAT(self, *args: list[Any]) -> str | None:
444
+ """CONCAT Function"""
445
+ for val in args:
446
+ if val is None:
447
+ return None
448
+
449
+ return ''.join([str(val) for val in args])
450
+
451
+ def RANDOM(self, *args: list[Any]) -> float | str:
452
+ """RANDOM Function"""
453
+ if len(args) > 2:
454
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
455
+ if len(args) < 2:
456
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
457
+
458
+ if args[0] is None or args[1] is None:
459
+ return None
460
+
461
+ import random
462
+
463
+ return random.random() * (float(args[1]) - float(args[0])) + float(args[0]) # type: ignore
464
+
465
+ def RANDOM_INT(self, *args: list[Any]) -> int | str:
466
+ """RANDOM_INT Function"""
467
+ if len(args) != 2:
468
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
469
+
470
+ if args[0] is None or args[1] is None:
471
+ return None
472
+
473
+ import random
474
+
475
+ return random.randint(int(args[0]), int(args[1])) # type: ignore
476
+
477
+ def GREATER_THAN_OR_EQUALS_TO(self, *args: list[Any]) -> str | None | bool:
478
+ """GREATER_THAN_OR_EQUALS_TO Function"""
479
+ if len(args) > 2:
480
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
481
+ if len(args) < 2:
482
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
483
+
484
+ if args[0] is None or args[1] is None:
485
+ return None
486
+
487
+ args = self._standarize_datatypes(args) # type: ignore
488
+
489
+ if not isinstance(args[0], type(args[1])):
490
+ return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
491
+ return args[0] >= args[1]
492
+
493
+ def GREATER_THAN(self, *args: list[Any]) -> str | None | bool:
494
+ """GREATER_THAN Function"""
495
+ if len(args) > 2:
496
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
497
+ if len(args) < 2:
498
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
499
+
500
+ if args[0] is None or args[1] is None:
501
+ return None
502
+
503
+ args = self._standarize_datatypes(args) # type: ignore
504
+
505
+ if not isinstance(args[0], type(args[1])):
506
+ return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
507
+ return args[0] > args[1]
508
+
509
+ def LESS_THAN_OR_EQUALS_TO(self, *args: list[Any]) -> str | None | bool:
510
+ """LESS_THAN_OR_EQUALS_TO Function"""
511
+ if len(args) > 2:
512
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
513
+ if len(args) < 2:
514
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
515
+
516
+ if args[0] is None or args[1] is None:
517
+ return None
518
+
519
+ args = self._standarize_datatypes(args) # type: ignore
520
+
521
+ if not isinstance(args[0], type(args[1])):
522
+ return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
523
+ return args[0] <= args[1]
524
+
525
+ def LESS_THAN(self, *args: list[Any]) -> str | None | bool:
526
+ """LESS_THAN Function"""
527
+ if len(args) > 2:
528
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
529
+ if len(args) < 2:
530
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
531
+
532
+ if args[0] is None or args[1] is None:
533
+ return None
534
+
535
+ args = self._standarize_datatypes(args) # type: ignore
536
+
537
+ if not isinstance(args[0], type(args[1])):
538
+ return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
539
+ return args[0] < args[1]
540
+
541
+ def DIFFERENT(self, *args: list[Any]) -> str | None | bool:
542
+ """DIFFERENT Function"""
543
+ if len(args) > 2:
544
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
545
+ if len(args) < 2:
546
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
547
+
548
+ if args[0] is None or args[1] is None:
549
+ return None
550
+
551
+ args = self._standarize_datatypes(args) # type: ignore
552
+
553
+ if not isinstance(args[0], type(args[1])):
554
+ return DIFFERENT_TYPES.format(arg1=type(args[0]).__name__, arg2=type(args[1]).__name__)
555
+ return args[0] != args[1]
556
+
557
+ def HEX_TO_STR(self, *args: list[Any]) -> str | None:
558
+ """HEX_TO_STR Function"""
559
+ if len(args) > 1:
560
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
561
+
562
+ if args[0] is None:
563
+ return None
564
+
565
+ hexa = args[0]
566
+ if hexa.startswith('0x'): # type: ignore
567
+ hexa = hexa[2:]
568
+
569
+ try:
570
+ byte_array = bytes.fromhex(hexa) # type: ignore
571
+ return byte_array.decode('ASCII')
572
+ except Exception:
573
+ return 'Invalid hex string'
574
+
575
+ def STR_TO_HEX(self, *args: list[Any]) -> str | None:
576
+ """STR_TO_HEX Function"""
577
+ if len(args) > 1:
578
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
579
+
580
+ if args[0] is None:
581
+ return None
582
+
583
+ return str(args[0]).encode('ASCII').hex()
584
+
585
+ def HEX_TO_INT(self, *args: list[Any]) -> str | None | int:
586
+ """HEX_TO_INT Function"""
587
+ if len(args) > 1:
588
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
589
+
590
+ if args[0] is None:
591
+ return None
592
+
593
+ try:
594
+ return int(int(args[0], 16)) # type: ignore
595
+ except Exception:
596
+ return 'Invalid hex string'
597
+
598
+ def INT_TO_HEX(self, *args: list[Any]) -> str | None:
599
+ """INT_TO_HEX Function"""
600
+ if len(args) > 1:
601
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
602
+
603
+ if args[0] is None:
604
+ return None
605
+
606
+ try:
607
+ return hex(int(args[0]))[2:] # type: ignore
608
+ except Exception:
609
+ return 'Invalid int value'
610
+
611
+ def TO_FLOAT(self, *args: list[Any]) -> str | None | float:
612
+ """TO_FLOAT Function"""
613
+ if len(args) > 1:
614
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
615
+
616
+ if args[0] is None:
617
+ return None
618
+
619
+ try:
620
+ return float(args[0]) # type: ignore
621
+ except Exception:
622
+ return f'Invalid arguments - must be real number, not {type(args[0]).__name__}'
623
+
624
+ def IS_PARAMETER_PRESENT(self, *args: list[Any]) -> str | bool:
625
+ """IS_PARAMETER_PRESENT Function"""
626
+ if len(args) > 1:
627
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
628
+
629
+ if args[0] is None:
630
+ return None
631
+
632
+ return args[0] in self._payload # type: ignore
633
+
634
+ def IS_SENSOR_PRESENT(self, *args: list[Any]) -> str | bool:
635
+ """IS_SENSOR_PRESENT Function"""
636
+ if len(args) > 1:
637
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
638
+
639
+ if args[0] is None:
640
+ return None
641
+
642
+ return args[0] in self._sensors # type: ignore
643
+
644
+ def INSIDE_RANGE(self, *args: list[Any]) -> str | None | bool:
645
+ """INSIDE_RANGE Function"""
646
+ if len(args) != 3:
647
+ return INVALID_NUMBER_OF_PARAMS.format(expected=3, received=len(args))
648
+
649
+ if args[0] is None or args[1] is None or args[2] is None:
650
+ return None
651
+
652
+ args = self._standarize_datatypes(args) # type: ignore
653
+
654
+ if not isinstance(args[0], type(args[1])):
655
+ return DIFFERENT_TYPES_RANGES.format(
656
+ arg1=type(args[0]).__name__,
657
+ arg2=type(args[1]).__name__,
658
+ arg3=type(args[2]).__name__,
659
+ )
660
+
661
+ return args[1] <= args[0] <= args[2]
662
+
663
+ def OUTSIDE_RANGE(self, *args: list[Any]) -> str | None | bool:
664
+ """OUTSIDE_RANGE Function"""
665
+ if len(args) != 3:
666
+ return INVALID_NUMBER_OF_PARAMS.format(expected=3, received=len(args))
667
+
668
+ if args[0] is None or args[1] is None or args[2] is None:
669
+ return None
670
+
671
+ args = self._standarize_datatypes(args) # type: ignore
672
+
673
+ if not isinstance(args[0], type(args[1])):
674
+ return DIFFERENT_TYPES_RANGES.format(
675
+ arg1=type(args[0]).__name__,
676
+ arg2=type(args[1]).__name__,
677
+ arg3=type(args[2]).__name__,
678
+ )
679
+
680
+ return not args[1] <= args[0] <= args[2]
681
+
682
+ def GET_TIME_DIFFERENCE(self, *args: list[Any]) -> str | float:
683
+ """GET_TIME_DIFFERENCE Function"""
684
+ if len(args) > 0:
685
+ return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
686
+
687
+ return self._asset_constants.get('timeElapsed', 0) # type: ignore
688
+
689
+ def IF(self, *args: list[Any]) -> Any:
690
+ """IF Function"""
691
+ if len(args) != 3:
692
+ return INVALID_NUMBER_OF_PARAMS.format(expected=3, received=len(args))
693
+
694
+ if args[0] is None:
695
+ return None
696
+
697
+ return args[1] if args[0] else args[2]
698
+
699
+ def NOW(self, *args: list[Any]) -> float:
700
+ """NOW Function"""
701
+ import zoneinfo
702
+ from datetime import datetime
703
+
704
+ return datetime.now(tz=zoneinfo.ZoneInfo('UTC')).timestamp()
705
+
706
+ def REGEX(self, *args: list[Any]) -> str | None | bool:
707
+ """REGEX Function"""
708
+ if len(args) != 2:
709
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
710
+
711
+ if args[0] is None or args[1] is None:
712
+ return None
713
+
714
+ if not isinstance(args[0], str):
715
+ return PATTERN_INVALID.format(received=type(args[0]))
716
+
717
+ import re
718
+
719
+ pattern = re.compile(args[1])
720
+ return bool(pattern.match(args[0]))
721
+
722
+ def IS_NONE(self, *args: list[Any]) -> str | bool:
723
+ """IS_NONE Function"""
724
+ if len(args) != 1:
725
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
726
+
727
+ return args[0] is None
728
+
729
+ def NOT(self, *args: list[Any]) -> str | bool:
730
+ """NOT Function"""
731
+ if len(args) != 1:
732
+ return INVALID_NUMBER_OF_PARAMS.format(expected=1, received=len(args))
733
+
734
+ if args[0] is None:
735
+ return None
736
+
737
+ return not args[0]
738
+
739
+ def CONTAINS(self, *args: list[Any]) -> str | bool:
740
+ """CONTAINS function"""
741
+ if len(args) != 2:
742
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
743
+
744
+ if args[0] is None or args[1] is None:
745
+ return None
746
+
747
+ return str(args[0]) in str(args[1])
748
+
749
+ def STARTS_WITH(self, *args: list[Any]) -> str | bool:
750
+ """STARTS_WITH function"""
751
+ if len(args) != 2:
752
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
753
+
754
+ if args[0] is None or args[1] is None:
755
+ return None
756
+
757
+ return str(args[1]).startswith(str(args[0]))
758
+
759
+ def ENDS_WITH(self, *args: list[Any]) -> str | bool:
760
+ """ENDS_WITH function"""
761
+ if len(args) != 2:
762
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
763
+
764
+ if args[0] is None or args[1] is None:
765
+ return None
766
+
767
+ return str(args[1]).endswith(str(args[0]))
768
+
769
+ def PRIMARY_DEVICE(self, *args: list[Any]) -> str:
770
+ """PRIMARY_DEVICE function"""
771
+ if len(args) > 0:
772
+ return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
773
+
774
+ return self._asset_constants.get('primaryDevice', None) # type: ignore
775
+
776
+ def SUBSTRING(self, *args: list[Any]) -> str:
777
+ """Get a substring from string (args[0])"""
778
+ if len(args) < 2:
779
+ return INVALID_NUMBER_OF_PARAMS.format(
780
+ expected=2,
781
+ received=len(args),
782
+ )
783
+
784
+ if len(args) > 3:
785
+ return INVALID_NUMBER_OF_PARAMS.format(
786
+ expected=3,
787
+ received=len(args),
788
+ )
789
+
790
+ if args[0] is None:
791
+ return None
792
+
793
+ if not isinstance(args[0], str):
794
+ return DIFFERENT_TYPES.format(arg1='str', arg2=type(args[0]).__name__)
795
+
796
+ if args[1] is None:
797
+ return None
798
+
799
+ if not isinstance(args[1], int):
800
+ return DIFFERENT_TYPES.format(arg1='int', arg2=type(args[1]).__name__)
801
+
802
+ if len(args) == 3:
803
+ if args[2] is None:
804
+ return None
805
+
806
+ if not isinstance(args[2], int):
807
+ return DIFFERENT_TYPES.format(arg1='str', arg2=type(args[2]).__name__)
808
+
809
+ return args[0][args[1] : args[2]]
810
+ return args[0][args[1] :]
811
+
812
+ def UNIX_TO_STR(self, *args: list[Any]) -> str:
813
+ """Convert UNIX timestamp date (args[0]) to format (args[1]) string"""
814
+ if len(args) < 2:
815
+ return INVALID_NUMBER_OF_PARAMS.format(expected=2, received=len(args))
816
+
817
+ if args[0] is None:
818
+ return None
819
+
820
+ if args[1] is None:
821
+ return None
822
+
823
+ import zoneinfo
824
+ from datetime import datetime
825
+
826
+ tz = zoneinfo.ZoneInfo('UTC')
827
+
828
+ if len(args) > 2:
829
+ if args[2] is None:
830
+ return None
831
+
832
+ try:
833
+ tz = zoneinfo.ZoneInfo(args[2]) # type: ignore
834
+ except zoneinfo.ZoneInfoNotFoundError:
835
+ tz = zoneinfo.ZoneInfo('UTC')
836
+
837
+ return datetime.fromtimestamp(int(args[0]), tz=zoneinfo.ZoneInfo('UTC')).astimezone(tz).strftime(args[1]) # type: ignore
838
+
839
+ def VERSION(self, *args: list[Any]) -> str:
840
+ """VERSION function"""
841
+ if len(args) > 0:
842
+ return INVALID_NUMBER_OF_PARAMS.format(expected=0, received=len(args))
843
+
844
+ import importlib.metadata
845
+
846
+ version = importlib.metadata.version('layrz_sdk')
847
+
848
+ return f'LCL {version} - Layrz SDK Runtime'