PyAutomationIO 1.0.3__tar.gz → 1.1.0__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.
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/PKG-INFO +1 -1
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/PyAutomationIO.egg-info/PKG-INFO +1 -1
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/core.py +7 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/datalogger.py +184 -3
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/tags/resources/tags.py +53 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/state_machine.py +2 -0
- pyautomationio-1.1.0/version.py +2 -0
- pyautomationio-1.0.3/version.py +0 -2
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/LICENSE +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/MANIFEST.in +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/PyAutomationIO.egg-info/SOURCES.txt +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/PyAutomationIO.egg-info/dependency_links.txt +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/PyAutomationIO.egg-info/requires.txt +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/PyAutomationIO.egg-info/top_level.txt +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/README.md +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/alarms/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/alarms/states.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/alarms/trigger.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/buffer.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/core.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/events.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/logs.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/machines.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/opcua.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/opcua_server.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/tags.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/dbmodels/users.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/extensions/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/extensions/api.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/extensions/cors.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/filter/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/iad/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/iad/frozen_data.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/iad/out_of_range.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/iad/outliers.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/core.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/events.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/logdict.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/logs.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/machines.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/opcua_server.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/logger/users.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/managers/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/managers/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/managers/db.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/managers/opcua_client.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/managers/state_machine.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/models.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/alarms/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/alarms/resources/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/alarms/resources/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/alarms/resources/summary.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/events/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/events/resources/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/events/resources/events.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/events/resources/logs.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/tags/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/tags/resources/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/models/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/models/roles.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/models/users.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/roles.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/users.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/roles.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/users.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/opcua/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/opcua/models.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/opcua/subscription.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/alarms_history.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/assets/styles.css +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/alarms_summary.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/db.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/filter.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/machines.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/machines_detailed.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/opcua.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/opcua_server.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/tags.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/trends.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/communications.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/alarms_summary.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/database.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/gaussian_filter.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/machines.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/opcua.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/opcua_server.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/tags.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/trends.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/database.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/filter.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/machines.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/machines_detailed.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/main.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/opcua_server.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/tags.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/trends.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/singleton.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tags/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tags/cvt.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tags/filter.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tags/tag.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tests/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tests/test_alarms.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tests/test_core.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tests/test_unit.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/tests/test_user.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/utils/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/utils/decorators.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/utils/npw.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/utils/observer.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/utils/units.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/adimentional.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/current.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/density.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/eng_time.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/force.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/length.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/mass.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/mass_flow.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/percentage.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/power.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/pressure.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/temperature.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/volume.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/variables/volumetric_flow.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/workers/__init__.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/workers/logger.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/workers/state_machine.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/workers/worker.py +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/requirements.txt +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/setup.cfg +0 -0
- {pyautomationio-1.0.3 → pyautomationio-1.1.0}/setup.py +0 -0
|
@@ -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"""
|
|
@@ -388,6 +388,186 @@ class DataLogger(BaseLogger):
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
@db_rollback
|
|
392
|
+
def read_tabular_data(self, start:str, stop:str, timezone:str, tags:list, sample_time:int, page:int=1, limit:int=20):
|
|
393
|
+
r"""
|
|
394
|
+
Get historical data in tabular format with pagination and forward-fill resampling.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
start (str): Start datetime string
|
|
398
|
+
stop (str): Stop datetime string
|
|
399
|
+
timezone (str): Timezone string
|
|
400
|
+
tags (list): List of tag names
|
|
401
|
+
sample_time (int): Sample time in seconds
|
|
402
|
+
page (int): Page number
|
|
403
|
+
limit (int): Items per page
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
dict: {
|
|
407
|
+
"data": [...],
|
|
408
|
+
"pagination": {...}
|
|
409
|
+
}
|
|
410
|
+
"""
|
|
411
|
+
if not self.is_history_logged:
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
if not self.check_connectivity():
|
|
415
|
+
return dict()
|
|
416
|
+
|
|
417
|
+
_timezone = pytz.timezone(timezone)
|
|
418
|
+
utc_timezone = pytz.UTC
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
start_dt = _timezone.localize(datetime.strptime(start, DATETIME_FORMAT)).astimezone(utc_timezone)
|
|
422
|
+
stop_dt = _timezone.localize(datetime.strptime(stop, DATETIME_FORMAT)).astimezone(utc_timezone)
|
|
423
|
+
start_ts = start_dt.timestamp()
|
|
424
|
+
stop_ts = stop_dt.timestamp()
|
|
425
|
+
except ValueError:
|
|
426
|
+
return dict()
|
|
427
|
+
|
|
428
|
+
# Calculate total records based on time range and sample time
|
|
429
|
+
total_duration = stop_ts - start_ts
|
|
430
|
+
if total_duration < 0:
|
|
431
|
+
return {"data": [], "pagination": {}}
|
|
432
|
+
|
|
433
|
+
total_records = math.floor(total_duration / sample_time) + 1
|
|
434
|
+
|
|
435
|
+
# Pagination calculations
|
|
436
|
+
if limit <= 0: limit = 20
|
|
437
|
+
if page <= 0: page = 1
|
|
438
|
+
|
|
439
|
+
total_pages = math.ceil(total_records / limit)
|
|
440
|
+
if total_pages == 0: total_pages = 1
|
|
441
|
+
|
|
442
|
+
has_next = page < total_pages
|
|
443
|
+
has_prev = page > 1
|
|
444
|
+
|
|
445
|
+
# Calculate start and end for current page
|
|
446
|
+
start_index = (page - 1) * limit
|
|
447
|
+
end_index = min(start_index + limit, total_records)
|
|
448
|
+
|
|
449
|
+
page_start_ts = start_ts + (start_index * sample_time)
|
|
450
|
+
page_end_ts = start_ts + ((end_index - 1) * sample_time)
|
|
451
|
+
|
|
452
|
+
# Query data needed for this page plus context for forward fill
|
|
453
|
+
# We need data up to page_end_ts.
|
|
454
|
+
# For forward fill, we need the last known value before or at page_start_ts for each tag.
|
|
455
|
+
|
|
456
|
+
data_points = []
|
|
457
|
+
current_ts = page_start_ts
|
|
458
|
+
|
|
459
|
+
# Pre-fetch data for optimization could be complex due to forward fill requirement across large gaps.
|
|
460
|
+
# However, we can query per tag or query all data in range.
|
|
461
|
+
# For efficiency with many tags/large range, we should query intelligently.
|
|
462
|
+
# But to guarantee "last known value", we might need to look back indefinitely if no recent data.
|
|
463
|
+
# A simple approach for now: Query "last value before or at page_start_ts" for each tag,
|
|
464
|
+
# and all values between page_start_ts and page_end_ts.
|
|
465
|
+
|
|
466
|
+
# 1. Get initial values (state at page_start_ts)
|
|
467
|
+
current_values = {}
|
|
468
|
+
# Convert float timestamp to datetime for Peewee comparison
|
|
469
|
+
current_dt = datetime.fromtimestamp(current_ts, pytz.UTC)
|
|
470
|
+
|
|
471
|
+
for tag_name in tags:
|
|
472
|
+
# Get the latest value <= current_dt
|
|
473
|
+
last_val_query = (TagValue
|
|
474
|
+
.select(TagValue.value)
|
|
475
|
+
.join(Tags)
|
|
476
|
+
.where((Tags.name == tag_name) & (TagValue.timestamp <= current_dt))
|
|
477
|
+
.order_by(TagValue.timestamp.desc())
|
|
478
|
+
.limit(1)
|
|
479
|
+
.dicts())
|
|
480
|
+
|
|
481
|
+
entry = list(last_val_query)
|
|
482
|
+
if entry:
|
|
483
|
+
current_values[tag_name] = entry[0]['value']
|
|
484
|
+
else:
|
|
485
|
+
current_values[tag_name] = None
|
|
486
|
+
|
|
487
|
+
# 2. Get changes within the page window
|
|
488
|
+
# We query all changes for these tags in the time window of the page
|
|
489
|
+
|
|
490
|
+
# Convert boundaries to datetime for Peewee
|
|
491
|
+
page_start_dt = datetime.fromtimestamp(page_start_ts, pytz.UTC)
|
|
492
|
+
page_end_dt = datetime.fromtimestamp(page_end_ts, pytz.UTC)
|
|
493
|
+
|
|
494
|
+
changes_query = (TagValue
|
|
495
|
+
.select(Tags.name, TagValue.value, TagValue.timestamp)
|
|
496
|
+
.join(Tags)
|
|
497
|
+
.where(
|
|
498
|
+
(Tags.name.in_(tags)) &
|
|
499
|
+
(TagValue.timestamp > page_start_dt) &
|
|
500
|
+
(TagValue.timestamp <= page_end_dt)
|
|
501
|
+
)
|
|
502
|
+
.order_by(TagValue.timestamp.asc())
|
|
503
|
+
.dicts())
|
|
504
|
+
|
|
505
|
+
# Organize changes by timestamp
|
|
506
|
+
changes_by_ts = defaultdict(dict)
|
|
507
|
+
for change in changes_query:
|
|
508
|
+
# timestamp comes as datetime from Peewee
|
|
509
|
+
ts_val = change['timestamp']
|
|
510
|
+
if isinstance(ts_val, datetime):
|
|
511
|
+
# Ensure it's timezone aware or treat as UTC if naive
|
|
512
|
+
if ts_val.tzinfo is None:
|
|
513
|
+
ts_val = utc_timezone.localize(ts_val)
|
|
514
|
+
ts = ts_val.timestamp()
|
|
515
|
+
else:
|
|
516
|
+
ts = float(ts_val)
|
|
517
|
+
|
|
518
|
+
changes_by_ts[ts][change['name']] = change['value']
|
|
519
|
+
|
|
520
|
+
# 3. Generate tabular data
|
|
521
|
+
# We iterate step by step. This might be slow if step is small and range is large,
|
|
522
|
+
# but we are limited by pagination 'limit' (e.g. 20 rows), so it's fast!
|
|
523
|
+
|
|
524
|
+
# We need to process from page_start_ts to page_end_ts in sample_time steps.
|
|
525
|
+
# BUT we have a list of changes.
|
|
526
|
+
# The simple way: Iterate steps.
|
|
527
|
+
|
|
528
|
+
# Optimization: We have 'limit' steps.
|
|
529
|
+
|
|
530
|
+
changes_iter = sorted(changes_by_ts.keys())
|
|
531
|
+
change_idx = 0
|
|
532
|
+
|
|
533
|
+
for i in range(end_index - start_index):
|
|
534
|
+
step_ts = page_start_ts + (i * sample_time)
|
|
535
|
+
|
|
536
|
+
# Update current_values with any changes that happened between last step and now (inclusive)
|
|
537
|
+
# Actually, standard sample-hold means at time T we have value at T.
|
|
538
|
+
# If multiple values in (T-1, T], usually the last one prevails or the one at T?
|
|
539
|
+
# Requirement: "retorne exactamente el timestamp... valor anterior mas cercano registrado" (forward fill)
|
|
540
|
+
# So at step_ts, value is the latest value where timestamp <= step_ts.
|
|
541
|
+
|
|
542
|
+
# Advance change_idx to consume all changes <= step_ts
|
|
543
|
+
while change_idx < len(changes_iter) and changes_iter[change_idx] <= step_ts:
|
|
544
|
+
ts = changes_iter[change_idx]
|
|
545
|
+
for tag, val in changes_by_ts[ts].items():
|
|
546
|
+
current_values[tag] = val
|
|
547
|
+
change_idx += 1
|
|
548
|
+
|
|
549
|
+
# Build row
|
|
550
|
+
dt_object = datetime.fromtimestamp(step_ts, pytz.UTC)
|
|
551
|
+
formatted_ts = dt_object.astimezone(_timezone).strftime(DATETIME_FORMAT)
|
|
552
|
+
|
|
553
|
+
row = {"timestamp": formatted_ts}
|
|
554
|
+
for tag in tags:
|
|
555
|
+
row[tag] = current_values.get(tag) # None if no value ever recorded
|
|
556
|
+
|
|
557
|
+
data_points.append(row)
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
"data": data_points,
|
|
561
|
+
"pagination": {
|
|
562
|
+
"page": page,
|
|
563
|
+
"limit": limit,
|
|
564
|
+
"total_records": total_records,
|
|
565
|
+
"total_pages": total_pages,
|
|
566
|
+
"has_next": has_next,
|
|
567
|
+
"has_prev": has_prev
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
391
571
|
def _agregate_data_every_seconds(self, query, result, seconds:int, timezone:str="UTC"):
|
|
392
572
|
r"""Documentation here
|
|
393
573
|
"""
|
|
@@ -620,17 +800,18 @@ class DataLoggerEngine(BaseEngine):
|
|
|
620
800
|
_query["parameters"]["tags"] = tags
|
|
621
801
|
return self.query(_query)
|
|
622
802
|
|
|
623
|
-
def
|
|
803
|
+
def read_tabular_data(self, start:str, stop:str, timezone:str, tags:list, sample_time:int, page:int=1, limit:int=20):
|
|
624
804
|
r"""
|
|
625
|
-
Get historical data in
|
|
805
|
+
Get historical data in tabular format with pagination on a thread-safe mechanism
|
|
626
806
|
"""
|
|
627
807
|
_query = dict()
|
|
628
|
-
_query["action"] = "
|
|
808
|
+
_query["action"] = "read_tabular_data"
|
|
629
809
|
_query["parameters"] = dict()
|
|
630
810
|
_query["parameters"]["start"] = start
|
|
631
811
|
_query["parameters"]["stop"] = stop
|
|
632
812
|
_query["parameters"]["timezone"] = timezone
|
|
633
813
|
_query["parameters"]["tags"] = tags
|
|
814
|
+
_query["parameters"]["sample_time"] = sample_time
|
|
634
815
|
_query["parameters"]["page"] = page
|
|
635
816
|
_query["parameters"]["limit"] = limit
|
|
636
817
|
return self.query(_query)
|
|
@@ -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
|
pyautomationio-1.0.3/version.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/alarms/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/alarms/resources/summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/events/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/__init__.py
RENAMED
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/models/__init__.py
RENAMED
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/models/roles.py
RENAMED
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/modules/users/resources/models/users.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/callbacks/machines_detailed.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyautomationio-1.0.3 → pyautomationio-1.1.0}/automation/pages/components/gaussian_filter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|