PyAutomationIO 1.0.3__tar.gz → 1.1.1__tar.gz

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.
Files changed (147) hide show
  1. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/PKG-INFO +1 -1
  2. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/PyAutomationIO.egg-info/PKG-INFO +1 -1
  3. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/core.py +17 -2
  4. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/alarms.py +30 -5
  5. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/events.py +30 -5
  6. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/alarms.py +11 -3
  7. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/datalogger.py +234 -3
  8. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/events.py +11 -3
  9. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/alarms/resources/summary.py +3 -1
  10. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/events/resources/events.py +3 -1
  11. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/tags/resources/tags.py +53 -0
  12. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/state_machine.py +2 -0
  13. pyautomationio-1.1.1/version.py +2 -0
  14. pyautomationio-1.0.3/version.py +0 -2
  15. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/LICENSE +0 -0
  16. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/MANIFEST.in +0 -0
  17. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/PyAutomationIO.egg-info/SOURCES.txt +0 -0
  18. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/PyAutomationIO.egg-info/dependency_links.txt +0 -0
  19. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/PyAutomationIO.egg-info/requires.txt +0 -0
  20. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/PyAutomationIO.egg-info/top_level.txt +0 -0
  21. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/README.md +0 -0
  22. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/__init__.py +0 -0
  23. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/alarms/__init__.py +0 -0
  24. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/alarms/states.py +0 -0
  25. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/alarms/trigger.py +0 -0
  26. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/buffer.py +0 -0
  27. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/__init__.py +0 -0
  28. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/core.py +0 -0
  29. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/logs.py +0 -0
  30. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/machines.py +0 -0
  31. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/opcua.py +0 -0
  32. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/opcua_server.py +0 -0
  33. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/tags.py +0 -0
  34. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/dbmodels/users.py +0 -0
  35. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/extensions/__init__.py +0 -0
  36. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/extensions/api.py +0 -0
  37. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/extensions/cors.py +0 -0
  38. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/filter/__init__.py +0 -0
  39. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/iad/__init__.py +0 -0
  40. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/iad/frozen_data.py +0 -0
  41. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/iad/out_of_range.py +0 -0
  42. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/iad/outliers.py +0 -0
  43. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/__init__.py +0 -0
  44. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/core.py +0 -0
  45. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/logdict.py +0 -0
  46. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/logs.py +0 -0
  47. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/machines.py +0 -0
  48. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/opcua_server.py +0 -0
  49. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/logger/users.py +0 -0
  50. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/managers/__init__.py +0 -0
  51. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/managers/alarms.py +0 -0
  52. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/managers/db.py +0 -0
  53. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/managers/opcua_client.py +0 -0
  54. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/managers/state_machine.py +0 -0
  55. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/models.py +0 -0
  56. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/__init__.py +0 -0
  57. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/alarms/__init__.py +0 -0
  58. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/alarms/resources/__init__.py +0 -0
  59. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/alarms/resources/alarms.py +0 -0
  60. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/events/__init__.py +0 -0
  61. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/events/resources/__init__.py +0 -0
  62. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/events/resources/logs.py +0 -0
  63. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/tags/__init__.py +0 -0
  64. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/tags/resources/__init__.py +0 -0
  65. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/__init__.py +0 -0
  66. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/resources/__init__.py +0 -0
  67. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/resources/models/__init__.py +0 -0
  68. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/resources/models/roles.py +0 -0
  69. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/resources/models/users.py +0 -0
  70. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/resources/roles.py +0 -0
  71. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/resources/users.py +0 -0
  72. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/roles.py +0 -0
  73. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/modules/users/users.py +0 -0
  74. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/opcua/__init__.py +0 -0
  75. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/opcua/models.py +0 -0
  76. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/opcua/subscription.py +0 -0
  77. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/__init__.py +0 -0
  78. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/alarms.py +0 -0
  79. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/alarms_history.py +0 -0
  80. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/assets/styles.css +0 -0
  81. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/__init__.py +0 -0
  82. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/alarms.py +0 -0
  83. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/alarms_summary.py +0 -0
  84. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/db.py +0 -0
  85. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/filter.py +0 -0
  86. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/machines.py +0 -0
  87. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/machines_detailed.py +0 -0
  88. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/opcua.py +0 -0
  89. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/opcua_server.py +0 -0
  90. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/tags.py +0 -0
  91. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/callbacks/trends.py +0 -0
  92. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/communications.py +0 -0
  93. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/__init__.py +0 -0
  94. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/alarms.py +0 -0
  95. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/alarms_summary.py +0 -0
  96. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/database.py +0 -0
  97. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/gaussian_filter.py +0 -0
  98. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/machines.py +0 -0
  99. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/opcua.py +0 -0
  100. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/opcua_server.py +0 -0
  101. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/tags.py +0 -0
  102. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/components/trends.py +0 -0
  103. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/database.py +0 -0
  104. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/filter.py +0 -0
  105. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/machines.py +0 -0
  106. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/machines_detailed.py +0 -0
  107. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/main.py +0 -0
  108. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/opcua_server.py +0 -0
  109. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/tags.py +0 -0
  110. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/pages/trends.py +0 -0
  111. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/singleton.py +0 -0
  112. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tags/__init__.py +0 -0
  113. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tags/cvt.py +0 -0
  114. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tags/filter.py +0 -0
  115. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tags/tag.py +0 -0
  116. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tests/__init__.py +0 -0
  117. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tests/test_alarms.py +0 -0
  118. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tests/test_core.py +0 -0
  119. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tests/test_unit.py +0 -0
  120. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/tests/test_user.py +0 -0
  121. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/utils/__init__.py +0 -0
  122. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/utils/decorators.py +0 -0
  123. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/utils/npw.py +0 -0
  124. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/utils/observer.py +0 -0
  125. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/utils/units.py +0 -0
  126. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/__init__.py +0 -0
  127. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/adimentional.py +0 -0
  128. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/current.py +0 -0
  129. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/density.py +0 -0
  130. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/eng_time.py +0 -0
  131. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/force.py +0 -0
  132. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/length.py +0 -0
  133. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/mass.py +0 -0
  134. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/mass_flow.py +0 -0
  135. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/percentage.py +0 -0
  136. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/power.py +0 -0
  137. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/pressure.py +0 -0
  138. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/temperature.py +0 -0
  139. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/volume.py +0 -0
  140. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/variables/volumetric_flow.py +0 -0
  141. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/workers/__init__.py +0 -0
  142. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/workers/logger.py +0 -0
  143. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/workers/state_machine.py +0 -0
  144. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/automation/workers/worker.py +0 -0
  145. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/requirements.txt +0 -0
  146. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/setup.cfg +0 -0
  147. {pyautomationio-1.0.3 → pyautomationio-1.1.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAutomationIO
3
- Version: 1.0.3
3
+ Version: 1.1.1
4
4
  Summary: A python library to develop automation continuous tasks using sync or async concurrent threads
5
5
  Home-page: https://github.com/know-ai/PyAutomation
6
6
  Author: KnowAI
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAutomationIO
3
- Version: 1.0.3
3
+ Version: 1.1.1
4
4
  Summary: A python library to develop automation continuous tasks using sync or async concurrent threads
5
5
  Home-page: https://github.com/know-ai/PyAutomation
6
6
  Author: KnowAI
@@ -373,6 +373,13 @@ class PyAutomation(Singleton):
373
373
  """
374
374
  return self.logger_engine.read_table(start, stop, timezone, tags, page, limit)
375
375
 
376
+ @logging_error_handler
377
+ def get_tabular_data(self, start:str, stop:str, timezone:str, tags:list, sample_time:int, page:int=1, limit:int=20):
378
+ r"""
379
+ Documentation here
380
+ """
381
+ return self.logger_engine.read_tabular_data(start, stop, timezone, tags, sample_time, page, limit)
382
+
376
383
  @logging_error_handler
377
384
  def get_segments(self):
378
385
  r"""
@@ -1363,6 +1370,10 @@ class PyAutomation(Singleton):
1363
1370
  Documentation here
1364
1371
  """
1365
1372
  if self.is_db_connected():
1373
+
1374
+ # Ensure pagination parameters are present or defaulted
1375
+ if 'page' not in fields: fields['page'] = 1
1376
+ if 'limit' not in fields: fields['limit'] = 20
1366
1377
 
1367
1378
  return self.alarms_engine.filter_alarm_summary_by(**fields)
1368
1379
 
@@ -1535,7 +1546,9 @@ class PyAutomation(Singleton):
1535
1546
  description:str="",
1536
1547
  greater_than_timestamp:datetime=None,
1537
1548
  less_than_timestamp:datetime=None,
1538
- timezone:str="UTC")->list:
1549
+ timezone:str="UTC",
1550
+ page:int=1,
1551
+ limit:int=20)->list:
1539
1552
  r"""
1540
1553
  Documentation here
1541
1554
  """
@@ -1550,7 +1563,9 @@ class PyAutomation(Singleton):
1550
1563
  classification=classification,
1551
1564
  greater_than_timestamp=greater_than_timestamp,
1552
1565
  less_than_timestamp=less_than_timestamp,
1553
- timezone=timezone
1566
+ timezone=timezone,
1567
+ page=page,
1568
+ limit=limit
1554
1569
  )
1555
1570
 
1556
1571
  return list()
@@ -448,11 +448,14 @@ class AlarmSummary(BaseModel):
448
448
  tags:list[str]=None,
449
449
  greater_than_timestamp:datetime=None,
450
450
  less_than_timestamp:datetime=None,
451
- timezone:str='UTC'
451
+ timezone:str='UTC',
452
+ page:int=1,
453
+ limit:int=20
452
454
  ):
453
455
  r"""
454
456
  Documentation here
455
457
  """
458
+ import math
456
459
  _timezone = pytz.timezone(timezone)
457
460
  query = cls.select()
458
461
 
@@ -478,11 +481,33 @@ class AlarmSummary(BaseModel):
478
481
  query = query.where(cls.alarm_time < less_than_timestamp)
479
482
 
480
483
  query = query.order_by(cls.id.desc())
481
- if not query.exists():
482
-
483
- return []
484
484
 
485
- return [alarm.serialize() for alarm in query]
485
+ total_records = query.count()
486
+
487
+ if limit <= 0: limit = 20
488
+ if page <= 0: page = 1
489
+
490
+ total_pages = math.ceil(total_records / limit)
491
+ if total_pages == 0: total_pages = 1
492
+
493
+ has_next = page < total_pages
494
+ has_prev = page > 1
495
+
496
+ paginated_query = query.paginate(page, limit)
497
+
498
+ data = [alarm.serialize() for alarm in paginated_query]
499
+
500
+ return {
501
+ "data": data,
502
+ "pagination": {
503
+ "page": page,
504
+ "limit": limit,
505
+ "total_records": total_records,
506
+ "total_pages": total_pages,
507
+ "has_next": has_next,
508
+ "has_prev": has_prev
509
+ }
510
+ }
486
511
 
487
512
  @classmethod
488
513
  def get_alarm_summary_comments(cls, id:int):
@@ -77,10 +77,14 @@ class Events(BaseModel):
77
77
  description:str="",
78
78
  message:str="",
79
79
  classification:str="",
80
- timezone:str='UTC'):
80
+ timezone:str='UTC',
81
+ page:int=1,
82
+ limit:int=20
83
+ ):
81
84
  r"""
82
85
  Documentation here
83
86
  """
87
+ import math
84
88
  _timezone = pytz.timezone(timezone)
85
89
  query = cls.select()
86
90
 
@@ -113,11 +117,32 @@ class Events(BaseModel):
113
117
 
114
118
  query = query.order_by(cls.id.desc())
115
119
 
116
- if not query.exists():
117
-
118
- return []
120
+ total_records = query.count()
121
+
122
+ if limit <= 0: limit = 20
123
+ if page <= 0: page = 1
124
+
125
+ total_pages = math.ceil(total_records / limit)
126
+ if total_pages == 0: total_pages = 1
127
+
128
+ has_next = page < total_pages
129
+ has_prev = page > 1
119
130
 
120
- return [event.serialize() for event in query]
131
+ paginated_query = query.paginate(page, limit)
132
+
133
+ data = [event.serialize() for event in paginated_query]
134
+
135
+ return {
136
+ "data": data,
137
+ "pagination": {
138
+ "page": page,
139
+ "limit": limit,
140
+ "total_records": total_records,
141
+ "total_pages": total_pages,
142
+ "has_next": has_next,
143
+ "has_prev": has_prev
144
+ }
145
+ }
121
146
 
122
147
  @classmethod
123
148
  def get_comments(cls, id:int):
@@ -119,7 +119,9 @@ class AlarmsLogger(BaseLogger):
119
119
  tags:list[str]=None,
120
120
  greater_than_timestamp:datetime=None,
121
121
  less_than_timestamp:datetime=None,
122
- timezone:str="UTC"
122
+ timezone:str="UTC",
123
+ page:int=1,
124
+ limit:int=20
123
125
  ):
124
126
  r"""
125
127
  Documentation here
@@ -138,7 +140,9 @@ class AlarmsLogger(BaseLogger):
138
140
  tags=tags,
139
141
  greater_than_timestamp=greater_than_timestamp,
140
142
  less_than_timestamp=less_than_timestamp,
141
- timezone=timezone
143
+ timezone=timezone,
144
+ page=page,
145
+ limit=limit
142
146
  )
143
147
 
144
148
  @db_rollback
@@ -326,7 +330,9 @@ class AlarmsLoggerEngine(BaseEngine):
326
330
  tags:list[int]=None,
327
331
  greater_than_timestamp:datetime=None,
328
332
  less_than_timestamp:datetime=None,
329
- timezone:str='UTC'
333
+ timezone:str='UTC',
334
+ page:int=1,
335
+ limit:int=20
330
336
  ):
331
337
 
332
338
  _query = dict()
@@ -338,6 +344,8 @@ class AlarmsLoggerEngine(BaseEngine):
338
344
  _query["parameters"]["greater_than_timestamp"] = greater_than_timestamp
339
345
  _query["parameters"]["less_than_timestamp"] = less_than_timestamp
340
346
  _query["parameters"]["timezone"] = timezone
347
+ _query["parameters"]["page"] = page
348
+ _query["parameters"]["limit"] = limit
341
349
 
342
350
  return self.query(_query)
343
351
 
@@ -5,6 +5,7 @@ This module implements a database logger for the CVT instance,
5
5
  will create a time-serie for each tag in a short memory data base.
6
6
  """
7
7
  import pytz, logging, math
8
+ from peewee import fn
8
9
  from collections import defaultdict
9
10
  from datetime import datetime
10
11
  from ..tags.tag import Tag
@@ -388,6 +389,235 @@ class DataLogger(BaseLogger):
388
389
  }
389
390
  }
390
391
 
392
+ @db_rollback
393
+ def read_tabular_data(self, start:str, stop:str, timezone:str, tags:list, sample_time:int, page:int=1, limit:int=20):
394
+ r"""
395
+ Get historical data in tabular format with pagination and forward-fill resampling.
396
+
397
+ Args:
398
+ start (str): Start datetime string
399
+ stop (str): Stop datetime string
400
+ timezone (str): Timezone string
401
+ tags (list): List of tag names
402
+ sample_time (int): Sample time in seconds
403
+ page (int): Page number
404
+ limit (int): Items per page
405
+
406
+ Returns:
407
+ dict: {
408
+ "data": [...],
409
+ "pagination": {...}
410
+ }
411
+ """
412
+ if not self.is_history_logged:
413
+ return None
414
+
415
+ if not self.check_connectivity():
416
+ return dict()
417
+
418
+ _timezone = pytz.timezone(timezone)
419
+ utc_timezone = pytz.UTC
420
+
421
+ try:
422
+ start_dt = _timezone.localize(datetime.strptime(start, DATETIME_FORMAT)).astimezone(utc_timezone)
423
+ stop_dt = _timezone.localize(datetime.strptime(stop, DATETIME_FORMAT)).astimezone(utc_timezone)
424
+ start_ts = start_dt.timestamp()
425
+ stop_ts = stop_dt.timestamp()
426
+ except ValueError:
427
+ return dict()
428
+
429
+ if sample_time <= 0:
430
+ return dict()
431
+
432
+ # Check for data presence to adjust start time if necessary
433
+ # 1. Check if there is any data BEFORE or AT start_dt (history)
434
+ has_history = (TagValue
435
+ .select()
436
+ .join(Tags)
437
+ .where(
438
+ (Tags.name.in_(tags)) &
439
+ (TagValue.timestamp <= start_dt)
440
+ )
441
+ .limit(1)
442
+ .count() > 0)
443
+
444
+ if not has_history:
445
+ # 2. If no history, find the first actual data point within the requested range
446
+ min_ts = (TagValue
447
+ .select(fn.Min(TagValue.timestamp))
448
+ .join(Tags)
449
+ .where(
450
+ (Tags.name.in_(tags)) &
451
+ (TagValue.timestamp >= start_dt) &
452
+ (TagValue.timestamp <= stop_dt) &
453
+ (TagValue.value.is_null(False))
454
+ )
455
+ .scalar())
456
+
457
+ if min_ts is None:
458
+ # No data in range and no history
459
+ return {"data": [], "pagination": {}}
460
+
461
+ # Adjust start to the first actual data point
462
+ # Ensure min_ts is timezone aware if needed, though scalar() returns DB format
463
+ if isinstance(min_ts, datetime):
464
+ if min_ts.tzinfo is None:
465
+ min_ts = utc_timezone.localize(min_ts)
466
+ start_dt = min_ts
467
+ start_ts = start_dt.timestamp()
468
+ elif isinstance(min_ts, (int, float)):
469
+ start_ts = float(min_ts)
470
+ start_dt = datetime.fromtimestamp(start_ts, pytz.UTC)
471
+
472
+ # Calculate total records based on time range and sample time
473
+ total_duration = stop_ts - start_ts
474
+ if total_duration < 0:
475
+ return {"data": [], "pagination": {}}
476
+
477
+ total_records = math.floor(total_duration / sample_time) + 1
478
+
479
+ # Pagination calculations
480
+ if limit <= 0: limit = 20
481
+ if page <= 0: page = 1
482
+
483
+ total_pages = math.ceil(total_records / limit)
484
+ if total_pages == 0: total_pages = 1
485
+
486
+ has_next = page < total_pages
487
+ has_prev = page > 1
488
+
489
+ # Calculate start and end for current page
490
+ start_index = (page - 1) * limit
491
+ end_index = min(start_index + limit, total_records)
492
+
493
+ page_start_ts = start_ts + (start_index * sample_time)
494
+ page_end_ts = start_ts + ((end_index - 1) * sample_time)
495
+
496
+ # Query data needed for this page plus context for forward fill
497
+ # We need data up to page_end_ts.
498
+ # For forward fill, we need the last known value before or at page_start_ts for each tag.
499
+
500
+ data_points = []
501
+ current_ts = page_start_ts
502
+
503
+ # Pre-fetch data for optimization could be complex due to forward fill requirement across large gaps.
504
+ # However, we can query per tag or query all data in range.
505
+ # For efficiency with many tags/large range, we should query intelligently.
506
+ # But to guarantee "last known value", we might need to look back indefinitely if no recent data.
507
+ # A simple approach for now: Query "last value before or at page_start_ts" for each tag,
508
+ # and all values between page_start_ts and page_end_ts.
509
+
510
+ # 1. Get initial values (state at page_start_ts)
511
+ current_values = {}
512
+ # Convert float timestamp to datetime for Peewee comparison
513
+ current_dt = datetime.fromtimestamp(current_ts, pytz.UTC)
514
+
515
+ for tag_name in tags:
516
+ # Get the latest value <= current_dt
517
+ last_val_query = (TagValue
518
+ .select(TagValue.value)
519
+ .join(Tags)
520
+ .where((Tags.name == tag_name) & (TagValue.timestamp <= current_dt))
521
+ .order_by(TagValue.timestamp.desc())
522
+ .limit(1)
523
+ .dicts())
524
+
525
+ entry = list(last_val_query)
526
+ if entry:
527
+ current_values[tag_name] = entry[0]['value']
528
+ else:
529
+ current_values[tag_name] = None
530
+
531
+ # 2. Get changes within the page window
532
+ # We query all changes for these tags in the time window of the page
533
+
534
+ # Convert boundaries to datetime for Peewee
535
+ page_start_dt = datetime.fromtimestamp(page_start_ts, pytz.UTC)
536
+ page_end_dt = datetime.fromtimestamp(page_end_ts, pytz.UTC)
537
+
538
+ changes_query = (TagValue
539
+ .select(Tags.name, TagValue.value, TagValue.timestamp)
540
+ .join(Tags)
541
+ .where(
542
+ (Tags.name.in_(tags)) &
543
+ (TagValue.timestamp > page_start_dt) &
544
+ (TagValue.timestamp <= page_end_dt) &
545
+ (TagValue.value.is_null(False))
546
+ )
547
+ .order_by(TagValue.timestamp.asc())
548
+ .dicts())
549
+
550
+ # Organize changes by timestamp
551
+ changes_by_ts = defaultdict(dict)
552
+ for change in changes_query:
553
+ # timestamp comes as datetime from Peewee
554
+ ts_val = change['timestamp']
555
+ if isinstance(ts_val, datetime):
556
+ # Ensure it's timezone aware or treat as UTC if naive
557
+ if ts_val.tzinfo is None:
558
+ ts_val = utc_timezone.localize(ts_val)
559
+ ts = ts_val.timestamp()
560
+ else:
561
+ ts = float(ts_val)
562
+
563
+ changes_by_ts[ts][change['name']] = change['value']
564
+
565
+ # 3. Generate tabular data
566
+ # We iterate step by step. This might be slow if step is small and range is large,
567
+ # but we are limited by pagination 'limit' (e.g. 20 rows), so it's fast!
568
+
569
+ # We need to process from page_start_ts to page_end_ts in sample_time steps.
570
+ # BUT we have a list of changes.
571
+ # The simple way: Iterate steps.
572
+
573
+ # Optimization: We have 'limit' steps.
574
+
575
+ changes_iter = sorted(changes_by_ts.keys())
576
+ change_idx = 0
577
+
578
+ for i in range(end_index - start_index):
579
+ step_ts = page_start_ts + (i * sample_time)
580
+
581
+ # Update current_values with any changes that happened between last step and now (inclusive)
582
+ # Actually, standard sample-hold means at time T we have value at T.
583
+ # If multiple values in (T-1, T], usually the last one prevails or the one at T?
584
+ # Requirement: "retorne exactamente el timestamp... valor anterior mas cercano registrado" (forward fill)
585
+ # So at step_ts, value is the latest value where timestamp <= step_ts.
586
+
587
+ # Advance change_idx to consume all changes <= step_ts
588
+ while change_idx < len(changes_iter) and changes_iter[change_idx] <= step_ts:
589
+ ts = changes_iter[change_idx]
590
+ for tag, val in changes_by_ts[ts].items():
591
+ current_values[tag] = val
592
+ change_idx += 1
593
+
594
+ # Build row
595
+ dt_object = datetime.fromtimestamp(step_ts, pytz.UTC)
596
+ formatted_ts = dt_object.astimezone(_timezone).strftime(DATETIME_FORMAT)
597
+
598
+ row = {"timestamp": formatted_ts}
599
+ has_data = False
600
+ for tag in tags:
601
+ val = current_values.get(tag)
602
+ row[tag] = val # None if no value ever recorded
603
+ if val is not None:
604
+ has_data = True
605
+
606
+ if has_data:
607
+ data_points.append(row)
608
+
609
+ return {
610
+ "data": data_points,
611
+ "pagination": {
612
+ "page": page,
613
+ "limit": limit,
614
+ "total_records": total_records,
615
+ "total_pages": total_pages,
616
+ "has_next": has_next,
617
+ "has_prev": has_prev
618
+ }
619
+ }
620
+
391
621
  def _agregate_data_every_seconds(self, query, result, seconds:int, timezone:str="UTC"):
392
622
  r"""Documentation here
393
623
  """
@@ -620,17 +850,18 @@ class DataLoggerEngine(BaseEngine):
620
850
  _query["parameters"]["tags"] = tags
621
851
  return self.query(_query)
622
852
 
623
- def read_table(self, start:str, stop:str, timezone:str, tags:list, page:int=1, limit:int=20):
853
+ def read_tabular_data(self, start:str, stop:str, timezone:str, tags:list, sample_time:int, page:int=1, limit:int=20):
624
854
  r"""
625
- Get historical data in table format with pagination on a thread-safe mechanism
855
+ Get historical data in tabular format with pagination on a thread-safe mechanism
626
856
  """
627
857
  _query = dict()
628
- _query["action"] = "read_table"
858
+ _query["action"] = "read_tabular_data"
629
859
  _query["parameters"] = dict()
630
860
  _query["parameters"]["start"] = start
631
861
  _query["parameters"]["stop"] = stop
632
862
  _query["parameters"]["timezone"] = timezone
633
863
  _query["parameters"]["tags"] = tags
864
+ _query["parameters"]["sample_time"] = sample_time
634
865
  _query["parameters"]["page"] = page
635
866
  _query["parameters"]["limit"] = limit
636
867
  return self.query(_query)
@@ -68,7 +68,9 @@ class EventsLogger(BaseLogger):
68
68
  classification:str="",
69
69
  greater_than_timestamp:datetime=None,
70
70
  less_than_timestamp:datetime=None,
71
- timezone:str="UTC"
71
+ timezone:str="UTC",
72
+ page:int=1,
73
+ limit:int=20
72
74
  ):
73
75
  r"""
74
76
  Documentation here
@@ -90,7 +92,9 @@ class EventsLogger(BaseLogger):
90
92
  description=description,
91
93
  greater_than_timestamp=greater_than_timestamp,
92
94
  less_than_timestamp=less_than_timestamp,
93
- timezone=timezone
95
+ timezone=timezone,
96
+ page=page,
97
+ limit=limit
94
98
  )
95
99
 
96
100
  def get_summary(self)->tuple[list, str]:
@@ -164,7 +168,9 @@ class EventsLoggerEngine(BaseEngine):
164
168
  description:str="",
165
169
  greater_than_timestamp:datetime=None,
166
170
  less_than_timestamp:datetime=None,
167
- timezone:str='UTC'
171
+ timezone:str='UTC',
172
+ page:int=1,
173
+ limit:int=20
168
174
  ):
169
175
 
170
176
  _query = dict()
@@ -179,6 +185,8 @@ class EventsLoggerEngine(BaseEngine):
179
185
  _query["parameters"]["greater_than_timestamp"] = greater_than_timestamp
180
186
  _query["parameters"]["less_than_timestamp"] = less_than_timestamp
181
187
  _query["parameters"]["timezone"] = timezone
188
+ _query["parameters"]["page"] = page
189
+ _query["parameters"]["limit"] = limit
182
190
 
183
191
  return self.query(_query)
184
192
 
@@ -16,7 +16,9 @@ alarms_summary_filter_model = api.model("alarms_summary_filter_model",{
16
16
  'tags': fields.List(fields.String(), required=False),
17
17
  'greater_than_timestamp': fields.DateTime(required=False, default=datetime.now(pytz.utc).astimezone(TIMEZONE) - timedelta(minutes=30), description=f'Greater than timestamp - DateTime Format: {app.cvt.DATETIME_FORMAT}'),
18
18
  'less_than_timestamp': fields.DateTime(required=False, default=datetime.now(pytz.utc).astimezone(TIMEZONE), description=f'Less than timestamp - DateTime Format: {app.cvt.DATETIME_FORMAT}'),
19
- 'timezone': fields.String(required=False, default=_TIMEZONE)
19
+ 'timezone': fields.String(required=False, default=_TIMEZONE),
20
+ 'page': fields.Integer(required=False, default=1, description='Page number'),
21
+ 'limit': fields.Integer(required=False, default=20, description='Items per page')
20
22
  })
21
23
 
22
24
 
@@ -20,7 +20,9 @@ events_filter_model = api.model("events_filter_model",{
20
20
  'description': fields.String(required=False),
21
21
  'greater_than_timestamp': fields.DateTime(required=False, default=datetime.now(pytz.utc).astimezone(TIMEZONE) - timedelta(minutes=30), description=f'Greater than timestamp - DateTime Format: {app.cvt.DATETIME_FORMAT}'),
22
22
  'less_than_timestamp': fields.DateTime(required=False, default=datetime.now(pytz.utc).astimezone(TIMEZONE), description=f'Less than timestamp - DateTime Format: {app.cvt.DATETIME_FORMAT}'),
23
- 'timezone': fields.String(required=False, default=_TIMEZONE)
23
+ 'timezone': fields.String(required=False, default=_TIMEZONE),
24
+ 'page': fields.Integer(required=False, default=1, description='Page number'),
25
+ 'limit': fields.Integer(required=False, default=20, description='Items per page')
24
26
  })
25
27
 
26
28
 
@@ -25,6 +25,16 @@ query_table_model = api.model("query_table_model",{
25
25
  'limit': fields.Integer(required=False, default=20, description='Items per page')
26
26
  })
27
27
 
28
+ query_tabular_data_model = api.model("query_tabular_data_model",{
29
+ 'tags': fields.List(fields.String(), required=True),
30
+ 'greater_than_timestamp': fields.DateTime(required=True, default=datetime.now(pytz.utc).astimezone(TIMEZONE) - timedelta(minutes=30), description='Greater than DateTime'),
31
+ 'less_than_timestamp': fields.DateTime(required=True, default=datetime.now(pytz.utc).astimezone(TIMEZONE), description='Less than DateTime'),
32
+ 'sample_time': fields.Integer(required=True, description='Sample time in seconds'),
33
+ 'timezone': fields.String(required=True, default=_TIMEZONE),
34
+ 'page': fields.Integer(required=False, default=1, description='Page number'),
35
+ 'limit': fields.Integer(required=False, default=20, description='Items per page')
36
+ })
37
+
28
38
  write_value_model = api.model("write_value_model", {
29
39
  'tag_name': fields.String(required=True, description='Nombre del tag'),
30
40
  'value': fields.Raw(required=True, description='Valor a escribir (float, int, bool, str)')
@@ -134,6 +144,49 @@ class QueryTableResource(Resource):
134
144
 
135
145
  return result, 200
136
146
 
147
+ @ns.route('/get_tabular_data')
148
+ class GetTabularDataResource(Resource):
149
+
150
+ @api.doc(security='apikey')
151
+ @Api.token_required(auth=True)
152
+ @ns.expect(query_tabular_data_model)
153
+ def post(self):
154
+ """
155
+ Query tag values in tabular format with pagination and resampling.
156
+
157
+ The result contains data points at regular intervals (sample_time) from greater_than_timestamp
158
+ up to less_than_timestamp. If exact data is missing, the previous known value is used (forward fill).
159
+
160
+ Authorized Roles: {0}
161
+ """
162
+ timezone = _TIMEZONE
163
+ tags = api.payload['tags']
164
+ page = api.payload.get('page', 1)
165
+ limit = api.payload.get('limit', 20)
166
+ sample_time = api.payload.get('sample_time', 60) # Default 1 min if not provided, but model requires it
167
+
168
+ if "timezone" in api.payload:
169
+ timezone = api.payload["timezone"]
170
+
171
+ if timezone not in pytz.all_timezones:
172
+ return f"Invalid Timezone", 400
173
+
174
+ for tag in tags:
175
+ if not app.get_tag_by_name(name=tag):
176
+ return f"{tag} not exist into db", 404
177
+
178
+ separator = '.'
179
+ greater_than_timestamp = api.payload['greater_than_timestamp']
180
+ # Ensure timestamp format is consistent
181
+ start = greater_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
182
+
183
+ less_than_timestamp = api.payload['less_than_timestamp']
184
+ stop = less_than_timestamp.replace("T", " ").split(separator, 1)[0] + '.00'
185
+
186
+ result = app.get_tabular_data(start, stop, timezone, tags, sample_time, page, limit)
187
+
188
+ return result, 200
189
+
137
190
  @ns.route('/write_value')
138
191
  class WriteValueResource(Resource):
139
192
 
@@ -457,6 +457,8 @@ class StateMachineCore(StateMachine):
457
457
  self.transitions = transitions
458
458
  self.manufacturer = MANUFACTURER
459
459
  self.segment = SEGMENT
460
+ self.mass_flow_unit_base = "kg/sec"
461
+ self.volumetric_flow_unit_base = "m3/sec"
460
462
  super(StateMachineCore, self).__init__()
461
463
 
462
464
  # State Methods
@@ -0,0 +1,2 @@
1
+ __version__ = '1.1.1'
2
+
@@ -1,2 +0,0 @@
1
- __version__ = '1.0.3'
2
-
File without changes
File without changes
File without changes
File without changes