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