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