Actualización de application_events.json con nuevos eventos para la creación y activación de datasets, así como la gestión de sesiones de plot. Se ajustaron las fechas de última actualización en varios archivos de configuración, incluyendo plc_config.json, plc_datasets.json y system_state.json. Se implementaron mejoras en la interfaz de usuario para la visualización de datos en tiempo real y se optimizó el código en plotting.js para una mejor gestión de gráficos.
This commit is contained in:
parent
8d693c48c7
commit
5e2149b9d4
|
@ -8145,8 +8145,882 @@
|
|||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:15:37.931252",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:15:37.995825",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:15:38.011841",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:15:53.487852",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 20,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:27:47.413936",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:27:47.482826",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:27:47.500264",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:32:23.076094",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:32:23.143386",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:32:23.158384",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:37:00.136346",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:37:00.187014",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:37:00.197221",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:39:03.481139",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 120,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:39:35.253297",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 500,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:41:40.082777",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:41:40.149440",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:41:40.164902",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:42:00.022025",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 50,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:47:39.354931",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:47:39.421225",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:47:39.436596",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:50:13.074173",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:50:13.159596",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:50:13.174855",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:53:53.601636",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:53:53.683028",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:53:53.697931",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:54:09.106165",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 10,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:54:12.772022",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 24,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:54:24.565758",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 5,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:55:13.651682",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_0",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 20,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:58:11.878053",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:58:11.947102",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T01:58:11.963722",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:02:17.438722",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:02:17.493178",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:02:17.504654",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:04:32.017747",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_removed",
|
||||
"message": "Plot session 'UR29' removed",
|
||||
"details": {
|
||||
"session_id": "plot_0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:05:35.486330",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_created",
|
||||
"message": "Plot session 'UR29' created and started",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 60,
|
||||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:06:26.841012",
|
||||
"level": "info",
|
||||
"event_type": "dataset_created",
|
||||
"message": "Dataset created: Fast (prefix: fast)",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"name": "Fast",
|
||||
"prefix": "fast",
|
||||
"sampling_interval": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:06:51.166985",
|
||||
"level": "info",
|
||||
"event_type": "dataset_csv_file_created",
|
||||
"message": "New CSV file created after variable modification for dataset 'DAR': gateway_phoenix_02_06_51.csv",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"file_path": "records\\09-08-2025\\gateway_phoenix_02_06_51.csv",
|
||||
"variables_count": 3,
|
||||
"reason": "variable_modification"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:06:51.178095",
|
||||
"level": "info",
|
||||
"event_type": "variable_added",
|
||||
"message": "Variable added to dataset 'DAR': fUR29_Brix -> DB1011.1322 (real)",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"name": "fUR29_Brix",
|
||||
"area": "db",
|
||||
"db": 1011,
|
||||
"offset": 1322,
|
||||
"bit": null,
|
||||
"type": "real",
|
||||
"streaming": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:07:40.947419",
|
||||
"level": "info",
|
||||
"event_type": "variable_added",
|
||||
"message": "Variable added to dataset 'Fast': fUR29_Brix -> DB1011.1322 (real)",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"name": "fUR29_Brix",
|
||||
"area": "db",
|
||||
"db": 1011,
|
||||
"offset": 1322,
|
||||
"bit": null,
|
||||
"type": "real",
|
||||
"streaming": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:08:07.444267",
|
||||
"level": "info",
|
||||
"event_type": "variable_added",
|
||||
"message": "Variable added to dataset 'Fast': fUR29_ma -> DB1011.1296 (real)",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"name": "fUR29_ma",
|
||||
"area": "db",
|
||||
"db": 1011,
|
||||
"offset": 1296,
|
||||
"bit": null,
|
||||
"type": "real",
|
||||
"streaming": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:08:26.500463",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 0,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T02:09:13.534886",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_created",
|
||||
"message": "Plot session 'Fast' created and started",
|
||||
"details": {
|
||||
"session_id": "plot_2",
|
||||
"variables": [
|
||||
"fUR29_ma",
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"fUR29_Brix"
|
||||
],
|
||||
"time_window": 60,
|
||||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T22:43:54.204995",
|
||||
"level": "info",
|
||||
"event_type": "datasets_resumed_after_reconnection",
|
||||
"message": "Automatically resumed streaming for 2 datasets after PLC reconnection",
|
||||
"details": {
|
||||
"resumed_datasets": 2,
|
||||
"total_attempted": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:22:59.642234",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:22:59.723755",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 3,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:22:59.737379",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 0,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:22:59.748890",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:27:02.476867",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'Fast' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_2",
|
||||
"new_config": {
|
||||
"name": "Fast",
|
||||
"variables": [
|
||||
"fUR29_ma",
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"fUR29_Brix"
|
||||
],
|
||||
"time_window": 10,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:42:15.949524",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:42:16.047945",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 3,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:42:16.061947",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 0,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-09T23:42:16.079052",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:32:18.359734",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'Fast' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_2",
|
||||
"new_config": {
|
||||
"name": "Fast",
|
||||
"variables": [
|
||||
"fUR29_ma",
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"fUR29_Brix"
|
||||
],
|
||||
"time_window": 60,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:32:50.990081",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_removed",
|
||||
"message": "Plot session 'Fast' removed",
|
||||
"details": {
|
||||
"session_id": "plot_2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:33:55.840458",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 65,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:34:06.009225",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"time_window": 75,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:34:26.104532",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"fUR29_Brix"
|
||||
],
|
||||
"time_window": 75,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:37:18.004596",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:37:18.089614",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 3,
|
||||
"streaming_count": 2,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:37:18.104615",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: Fast",
|
||||
"details": {
|
||||
"dataset_id": "Fast",
|
||||
"variables_count": 2,
|
||||
"streaming_count": 0,
|
||||
"prefix": "fast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:37:18.121614",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 2 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 2,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-10T00:37:46.526185",
|
||||
"level": "info",
|
||||
"event_type": "plot_session_updated",
|
||||
"message": "Plot session 'UR29' configuration updated",
|
||||
"details": {
|
||||
"session_id": "plot_1",
|
||||
"new_config": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"fUR29_Brix",
|
||||
"fUR29_ma"
|
||||
],
|
||||
"time_window": 75,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-09T01:04:33.328051",
|
||||
"total_entries": 767
|
||||
"last_updated": "2025-08-10T00:37:46.526185",
|
||||
"total_entries": 834
|
||||
}
|
|
@ -35,6 +35,12 @@ class PLCClient:
|
|||
[]
|
||||
) # List of callback functions for disconnection tracking
|
||||
|
||||
# Global I/O serialization to avoid concurrent snap7 calls
|
||||
# Acts as a simple read queue to prevent 'CLI : Job pending'
|
||||
self.io_lock = threading.RLock()
|
||||
# Small inter-read delay to give PLC time between requests (seconds)
|
||||
self.inter_read_delay_seconds = 0.002
|
||||
|
||||
def connect(self, ip: str, rack: int, slot: int) -> bool:
|
||||
"""Connect to S7-315 PLC"""
|
||||
try:
|
||||
|
@ -223,65 +229,86 @@ class PLCClient:
|
|||
self.reconnection_thread.start()
|
||||
|
||||
def read_variable(self, var_config: Dict[str, Any]) -> Any:
|
||||
"""Read a specific variable from the PLC"""
|
||||
"""Read a specific variable from the PLC, serialized across threads"""
|
||||
if not self.is_connected():
|
||||
return None
|
||||
|
||||
try:
|
||||
area_type = var_config.get("area", "db").lower()
|
||||
offset = var_config["offset"]
|
||||
var_type = var_config["type"]
|
||||
bit = var_config.get("bit")
|
||||
# Ensure only one snap7 operation at a time
|
||||
with self.io_lock:
|
||||
try:
|
||||
area_type = var_config.get("area", "db").lower()
|
||||
offset = var_config["offset"]
|
||||
var_type = var_config["type"]
|
||||
bit = var_config.get("bit")
|
||||
|
||||
if area_type == "db":
|
||||
return self._read_db_variable(var_config, offset, var_type, bit)
|
||||
elif area_type in ["mw", "m"]:
|
||||
return self._read_memory_variable(offset, var_type)
|
||||
elif area_type in ["pew", "pe"]:
|
||||
return self._read_input_variable(offset, var_type)
|
||||
elif area_type in ["paw", "pa"]:
|
||||
return self._read_output_variable(offset, var_type)
|
||||
elif area_type == "e":
|
||||
return self._read_input_bit(offset, bit)
|
||||
elif area_type == "a":
|
||||
return self._read_output_bit(offset, bit)
|
||||
elif area_type == "mb":
|
||||
return self._read_memory_bit(offset, bit)
|
||||
else:
|
||||
if self.logger:
|
||||
self.logger.error(f"Unsupported area type: {area_type}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.error(f"Error reading variable: {e}")
|
||||
|
||||
# Check if this is a connection error and start automatic reconnection
|
||||
if self._is_connection_error(str(e)):
|
||||
was_connected_before = self.connected
|
||||
self.connected = False
|
||||
self.consecutive_failures += 1
|
||||
|
||||
if self.logger:
|
||||
failure_num = self.consecutive_failures
|
||||
msg = (
|
||||
"Connection error detected, starting automatic "
|
||||
f"reconnection (failure #{failure_num})"
|
||||
if area_type == "db":
|
||||
result = self._read_db_variable(
|
||||
var_config,
|
||||
offset,
|
||||
var_type,
|
||||
bit,
|
||||
)
|
||||
self.logger.warning(msg)
|
||||
|
||||
# If we were connected before, notify disconnection callbacks FIRST
|
||||
if was_connected_before:
|
||||
elif area_type in ["mw", "m"]:
|
||||
result = self._read_memory_variable(offset, var_type)
|
||||
elif area_type in [
|
||||
"pew",
|
||||
"pe",
|
||||
]:
|
||||
result = self._read_input_variable(offset, var_type)
|
||||
elif area_type in [
|
||||
"paw",
|
||||
"pa",
|
||||
]:
|
||||
result = self._read_output_variable(offset, var_type)
|
||||
elif area_type == "e":
|
||||
result = self._read_input_bit(offset, bit)
|
||||
elif area_type == "a":
|
||||
result = self._read_output_bit(offset, bit)
|
||||
elif area_type == "mb":
|
||||
result = self._read_memory_bit(offset, bit)
|
||||
else:
|
||||
if self.logger:
|
||||
self.logger.info(
|
||||
"Notifying disconnection callbacks for dataset tracking"
|
||||
self.logger.error(f"Unsupported area type: {area_type}")
|
||||
result = None
|
||||
|
||||
# Small pacing delay between PLC I/O to avoid job overlap
|
||||
if self.inter_read_delay_seconds and self.inter_read_delay_seconds > 0:
|
||||
time.sleep(self.inter_read_delay_seconds)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.error(f"Error reading variable: {e}")
|
||||
|
||||
# Check if this is a connection error and start automatic reconnection
|
||||
if self._is_connection_error(str(e)):
|
||||
was_connected_before = self.connected
|
||||
self.connected = False
|
||||
self.consecutive_failures += 1
|
||||
|
||||
if self.logger:
|
||||
failure_num = self.consecutive_failures
|
||||
msg = (
|
||||
"Connection error detected, starting automatic "
|
||||
f"reconnection (failure #{failure_num})"
|
||||
)
|
||||
self._notify_disconnection_detected()
|
||||
self.logger.warning(msg)
|
||||
|
||||
# Start automatic reconnection in background
|
||||
self._start_automatic_reconnection()
|
||||
# If we were connected before, notify disconnection
|
||||
# callbacks FIRST
|
||||
if was_connected_before:
|
||||
if self.logger:
|
||||
self.logger.info(
|
||||
"Notifying disconnection callbacks for "
|
||||
"dataset tracking"
|
||||
)
|
||||
self._notify_disconnection_detected()
|
||||
|
||||
return None
|
||||
# Start automatic reconnection in background
|
||||
self._start_automatic_reconnection()
|
||||
|
||||
return None
|
||||
|
||||
def _read_db_variable(
|
||||
self, var_config: Dict[str, Any], offset: int, var_type: str, bit: Optional[int]
|
||||
|
@ -451,7 +478,8 @@ class PLCClient:
|
|||
- DataStreamer.read_dataset_variables() during streaming cycles
|
||||
- Internal diagnostic operations
|
||||
|
||||
Public APIs should use cached values via DataStreamer.get_cached_dataset_values()
|
||||
Public APIs should use cached values via
|
||||
DataStreamer.get_cached_dataset_values()
|
||||
"""
|
||||
if not self.is_connected():
|
||||
return {}
|
||||
|
@ -478,9 +506,10 @@ class PLCClient:
|
|||
- DataStreamer.read_dataset_variables() during streaming cycles
|
||||
- Internal diagnostic operations
|
||||
|
||||
Public APIs should use cached values via DataStreamer.get_cached_dataset_values()
|
||||
according to the application's single-read principle where variables are read
|
||||
only once per dataset interval and cached for all other uses.
|
||||
Public APIs should use cached values via
|
||||
DataStreamer.get_cached_dataset_values()
|
||||
according to the application's single-read principle where variables
|
||||
are read only once per dataset interval and cached for all other uses.
|
||||
"""
|
||||
if not self.is_connected():
|
||||
return {
|
||||
|
@ -550,7 +579,7 @@ class PLCClient:
|
|||
}
|
||||
elif success_count < total_count:
|
||||
warning_msg = (
|
||||
f"Partial success: {success_count}/{total_count} " "variables read"
|
||||
f"Partial success: {success_count}/{total_count} " f"variables read"
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
|
|
|
@ -481,11 +481,15 @@ class DataStreamer:
|
|||
try:
|
||||
start_time = time.time()
|
||||
|
||||
# Read variables for this dataset
|
||||
# Read variables for this dataset (serialized across datasets)
|
||||
dataset_variables = self.config_manager.get_dataset_variables(
|
||||
dataset_id
|
||||
)
|
||||
all_data = self.read_dataset_variables(dataset_id, dataset_variables)
|
||||
# Ensure entire dataset read is atomic w.r.t. other datasets
|
||||
with self.plc_client.io_lock:
|
||||
all_data = self.read_dataset_variables(
|
||||
dataset_id, dataset_variables
|
||||
)
|
||||
|
||||
if all_data:
|
||||
consecutive_errors = 0
|
||||
|
|
|
@ -16,6 +16,6 @@
|
|||
"max_days": 30,
|
||||
"max_hours": null,
|
||||
"cleanup_interval_hours": 24,
|
||||
"last_cleanup": "2025-08-08T15:50:27.922821"
|
||||
"last_cleanup": "2025-08-09T22:43:54.224975"
|
||||
}
|
||||
}
|
|
@ -17,6 +17,13 @@
|
|||
"type": "real",
|
||||
"streaming": true,
|
||||
"db": 1011
|
||||
},
|
||||
"fUR29_Brix": {
|
||||
"area": "db",
|
||||
"offset": 1322,
|
||||
"type": "real",
|
||||
"streaming": false,
|
||||
"db": 1011
|
||||
}
|
||||
},
|
||||
"streaming_variables": [
|
||||
|
@ -26,12 +33,37 @@
|
|||
"sampling_interval": 1.0,
|
||||
"enabled": true,
|
||||
"created": "2025-08-08T15:47:18.566053"
|
||||
},
|
||||
"Fast": {
|
||||
"name": "Fast",
|
||||
"prefix": "fast",
|
||||
"variables": {
|
||||
"fUR29_Brix": {
|
||||
"area": "db",
|
||||
"offset": 1322,
|
||||
"type": "real",
|
||||
"streaming": false,
|
||||
"db": 1011
|
||||
},
|
||||
"fUR29_ma": {
|
||||
"area": "db",
|
||||
"offset": 1296,
|
||||
"type": "real",
|
||||
"streaming": false,
|
||||
"db": 1011
|
||||
}
|
||||
},
|
||||
"streaming_variables": [],
|
||||
"sampling_interval": 0.1,
|
||||
"enabled": true,
|
||||
"created": "2025-08-09T02:06:26.840011"
|
||||
}
|
||||
},
|
||||
"active_datasets": [
|
||||
"DAR"
|
||||
"DAR",
|
||||
"Fast"
|
||||
],
|
||||
"current_dataset_id": "DAR",
|
||||
"current_dataset_id": "Fast",
|
||||
"version": "1.0",
|
||||
"last_update": "2025-08-09T01:04:33.316045"
|
||||
"last_update": "2025-08-10T00:37:18.103618"
|
||||
}
|
|
@ -1,21 +1,23 @@
|
|||
{
|
||||
"plots": {
|
||||
"plot_0": {
|
||||
"plot_1": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
"UR29_ma",
|
||||
"fUR29_Brix",
|
||||
"fUR29_ma"
|
||||
],
|
||||
"time_window": 50,
|
||||
"time_window": 75,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true,
|
||||
"session_id": "plot_0"
|
||||
"session_id": "plot_1"
|
||||
}
|
||||
},
|
||||
"session_counter": 1,
|
||||
"last_saved": "2025-08-09T00:59:52.219460",
|
||||
"session_counter": 2,
|
||||
"last_saved": "2025-08-10T00:37:46.525175",
|
||||
"version": "1.0"
|
||||
}
|
|
@ -545,7 +545,8 @@ textarea {
|
|||
border: var(--pico-border-width) solid var(--pico-border-color);
|
||||
border-radius: var(--pico-border-radius);
|
||||
margin-bottom: 1rem;
|
||||
overflow: hidden;
|
||||
/* Evitar recortar etiquetas inferiores del eje del chart */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.plot-header {
|
||||
|
@ -631,7 +632,9 @@ textarea {
|
|||
|
||||
.plot-canvas canvas {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
/* Permitir a Chart.js ajustar el alto con precisión */
|
||||
height: 100%;
|
||||
display: block;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ class PlotManager {
|
|||
</span>
|
||||
</div>
|
||||
<div class="plot-canvas">
|
||||
<canvas id="chart-${sessionId}" style="height: 400px;"></canvas>
|
||||
<canvas id="chart-${sessionId}"></canvas>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -196,9 +196,13 @@ class PlotManager {
|
|||
backgroundColor: color + '20',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 3,
|
||||
tension: 0.1
|
||||
// Unir huecos por defecto; los cortes reales los forzamos con NaN al reanudar
|
||||
spanGaps: true,
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 4,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
// stepped: true,
|
||||
tension: 0.4
|
||||
});
|
||||
});
|
||||
chartConfig.data.datasets = datasets;
|
||||
|
@ -224,7 +228,10 @@ class PlotManager {
|
|||
lastDataFetch: 0,
|
||||
datasetIndex: new Map(), // Mapeo de variable -> índice de dataset
|
||||
isRealTimeMode: isRealTimeMode,
|
||||
lastPushedXByDataset: new Map() // índice de dataset -> último timestamp x añadido
|
||||
lastPushedXByDataset: new Map(), // índice de dataset -> último timestamp x añadido
|
||||
ingestPaused: false, // si true, no se agregan puntos nuevos (marca hueco)
|
||||
insertNaNOnNextIngest: false,
|
||||
isPaused: false
|
||||
});
|
||||
|
||||
// Fijar refresh de datos base (no de render). El render es 30 FPS en el plugin.
|
||||
|
@ -260,6 +267,9 @@ class PlotManager {
|
|||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: { bottom: 16 }
|
||||
},
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
|
@ -274,6 +284,21 @@ class PlotManager {
|
|||
this.onStreamingRefresh(sessionId, chart);
|
||||
}
|
||||
},
|
||||
// Asegurar etiquetas de tiempo visibles y formateadas
|
||||
ticks: {
|
||||
display: true,
|
||||
autoSkip: true,
|
||||
maxRotation: 0
|
||||
},
|
||||
// Formatos de visualización para diferentes unidades de tiempo
|
||||
time: {
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'HH:mm'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Tiempo'
|
||||
|
@ -354,6 +379,9 @@ class PlotManager {
|
|||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: { bottom: 16 }
|
||||
},
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
|
@ -364,6 +392,11 @@ class PlotManager {
|
|||
second: 'HH:mm:ss'
|
||||
}
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
autoSkip: true,
|
||||
maxRotation: 0
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Tiempo'
|
||||
|
@ -451,6 +484,7 @@ class PlotManager {
|
|||
backgroundColor: color + '20',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
spanGaps: true,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 3,
|
||||
tension: 0.1
|
||||
|
@ -478,7 +512,7 @@ class PlotManager {
|
|||
// Evitar llamadas muy frecuentes basado en el refresh rate dinámico
|
||||
const now = Date.now();
|
||||
const refreshRate = this.refreshRates.get(sessionId) || 1000;
|
||||
const minInterval = Math.max(refreshRate * 0.8, 100); // 80% del refresh rate, mínimo 100ms
|
||||
const minInterval = Math.max(refreshRate * 0.5, 50); // menos restrictivo para alta frecuencia
|
||||
|
||||
if (now - sessionData.lastDataFetch < minInterval) {
|
||||
const timeSinceLastUpdate = now - sessionData.lastDataFetch;
|
||||
|
@ -517,29 +551,68 @@ class PlotManager {
|
|||
const chart = sessionData.chart;
|
||||
const isRealTimeMode = sessionData.isRealTimeMode;
|
||||
|
||||
// Si está en pausa, no ingerir datos ni empujar puntos
|
||||
try {
|
||||
if (isRealTimeMode) {
|
||||
const xScale = chart.scales && chart.scales.x;
|
||||
if (xScale && xScale.realtime && xScale.realtime.pause) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (sessionData.ingestPaused) {
|
||||
return;
|
||||
}
|
||||
} catch (_) { /* noop */ }
|
||||
|
||||
// En modo realtime, dejamos el panning continuo al plugin mediante 'delay'.
|
||||
// Solo empujamos puntos cuando haya datos nuevos del backend, usando su timestamp real.
|
||||
// Empujar todos los puntos nuevos disponibles para minimizar huecos.
|
||||
let pointsAdded = 0;
|
||||
chart.data.datasets.forEach((chartDataset, datasetIndex) => {
|
||||
const lastPushedX = sessionData.lastPushedXByDataset.get(datasetIndex) || 0;
|
||||
|
||||
const backendDataset = plotData && plotData.datasets ? plotData.datasets[datasetIndex] : undefined;
|
||||
if (!backendDataset || !backendDataset.data || backendDataset.data.length === 0) {
|
||||
if (!backendDataset || !Array.isArray(backendDataset.data) || backendDataset.data.length === 0) {
|
||||
return; // No hay datos nuevos para este dataset
|
||||
}
|
||||
|
||||
const latestPoint = backendDataset.data[backendDataset.data.length - 1];
|
||||
if (!latestPoint || latestPoint.y === null || latestPoint.y === undefined) {
|
||||
return; // Punto inválido
|
||||
// Filtrar y normalizar puntos con y válido y x en ms
|
||||
const newPoints = [];
|
||||
for (let i = 0; i < backendDataset.data.length; i++) {
|
||||
const p = backendDataset.data[i];
|
||||
const yNum = (typeof p.y === 'number') ? p.y : Number(p.y);
|
||||
if (!isFinite(yNum)) continue;
|
||||
let xNum = (typeof p.x === 'number') ? p.x : Number(p.x);
|
||||
if (!isFinite(xNum)) continue;
|
||||
if (xNum < 1e12) xNum = xNum * 1000; // segundos -> ms
|
||||
if (xNum > lastPushedX) newPoints.push({ x: xNum, y: yNum });
|
||||
}
|
||||
|
||||
// Garantizar monotonicidad en X sin forzar Date.now(): usar timestamp real del backend
|
||||
const candidateX = typeof latestPoint.x === 'number' ? latestPoint.x : 0;
|
||||
const finalX = Math.max(candidateX, lastPushedX + 1);
|
||||
if (newPoints.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
chartDataset.data.push({ x: finalX, y: latestPoint.y });
|
||||
sessionData.lastPushedXByDataset.set(datasetIndex, finalX);
|
||||
pointsAdded++;
|
||||
// Ordenar por x y garantizar monotonicidad con el último x
|
||||
newPoints.sort((a, b) => a.x - b.x);
|
||||
|
||||
// Insertar NaN solo una vez al reanudar, antes del primer punto nuevo
|
||||
if (sessionData.insertNaNOnNextIngest) {
|
||||
const firstX = Math.max(newPoints[0].x, lastPushedX + 1);
|
||||
let gapX = firstX - 1;
|
||||
if (gapX <= lastPushedX) {
|
||||
gapX = firstX - 0.001; // asegurar x estrictamente mayor que lastPushedX
|
||||
}
|
||||
chartDataset.data.push({ x: gapX, y: NaN });
|
||||
}
|
||||
|
||||
// Empujar todos los puntos nuevos ajustando x para ser estrictamente creciente
|
||||
let lastX = lastPushedX;
|
||||
for (const p of newPoints) {
|
||||
const finalX = Math.max(p.x, lastX + 1);
|
||||
chartDataset.data.push({ x: finalX, y: p.y });
|
||||
lastX = finalX;
|
||||
pointsAdded++;
|
||||
}
|
||||
sessionData.lastPushedXByDataset.set(datasetIndex, lastX);
|
||||
});
|
||||
|
||||
// En modo fallback (sin realtime), limpieza y update manual
|
||||
|
@ -551,6 +624,11 @@ class PlotManager {
|
|||
// Referencia: guía oficial (Pull Model - Asynchronous)
|
||||
chart.update('quiet');
|
||||
}
|
||||
|
||||
// Si se insertaron puntos en este ciclo, limpiar la marca para no seguir insertando NaN
|
||||
if (pointsAdded > 0 && sessionData.insertNaNOnNextIngest) {
|
||||
sessionData.insertNaNOnNextIngest = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -667,16 +745,27 @@ class PlotManager {
|
|||
|
||||
if (sessionData.isRealTimeMode) {
|
||||
// Modo realtime - pausar escala
|
||||
const xScale = sessionData.chart.scales.x;
|
||||
const chart = sessionData.chart;
|
||||
const xScale = chart.scales.x;
|
||||
if (xScale && xScale.realtime) {
|
||||
xScale.realtime.pause = true;
|
||||
}
|
||||
if (chart.options && chart.options.scales && chart.options.scales.x && chart.options.scales.x.realtime) {
|
||||
chart.options.scales.x.realtime.pause = true;
|
||||
}
|
||||
chart.update('quiet');
|
||||
|
||||
// Marcar pausa de ingesta para crear hueco (NaN) en reanudación
|
||||
sessionData.ingestPaused = true;
|
||||
sessionData.isPaused = true;
|
||||
} else {
|
||||
// Modo fallback - pausar intervalo manual
|
||||
if (sessionData.manualInterval) {
|
||||
clearInterval(sessionData.manualInterval);
|
||||
sessionData.manualInterval = null;
|
||||
}
|
||||
sessionData.ingestPaused = true;
|
||||
sessionData.isPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -689,16 +778,25 @@ class PlotManager {
|
|||
|
||||
if (sessionData.isRealTimeMode) {
|
||||
// Modo realtime - reanudar escala
|
||||
const xScale = sessionData.chart.scales.x;
|
||||
const chart = sessionData.chart;
|
||||
const xScale = chart.scales.x;
|
||||
if (xScale && xScale.realtime) {
|
||||
xScale.realtime.pause = false;
|
||||
}
|
||||
if (chart.options && chart.options.scales && chart.options.scales.x && chart.options.scales.x.realtime) {
|
||||
chart.options.scales.x.realtime.pause = false;
|
||||
}
|
||||
chart.update('quiet');
|
||||
} else {
|
||||
// Modo fallback - reanudar intervalo manual
|
||||
if (!sessionData.manualInterval) {
|
||||
this.startManualRefresh(sessionId);
|
||||
}
|
||||
}
|
||||
// Marcar para insertar un único NaN en la próxima ingesta
|
||||
sessionData.insertNaNOnNextIngest = true;
|
||||
sessionData.ingestPaused = false;
|
||||
sessionData.isPaused = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1449,42 +1547,37 @@ class PlotManager {
|
|||
let sessionId = null;
|
||||
|
||||
if (this.currentEditingSession) {
|
||||
// Modo edición: ELIMINAR el plot existente y crear uno nuevo desde cero
|
||||
|
||||
// 1. Eliminar el plot existente
|
||||
const deleteResponse = await fetch(`/api/plots/${this.currentEditingSession}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!deleteResponse.ok) {
|
||||
throw new Error('Failed to delete existing plot');
|
||||
}
|
||||
|
||||
// 2. Remover de la UI
|
||||
if (typeof tabManager !== 'undefined') {
|
||||
tabManager.removePlotTab(this.currentEditingSession);
|
||||
}
|
||||
|
||||
// 3. Remover del PlotManager
|
||||
if (this.sessions.has(this.currentEditingSession)) {
|
||||
const chart = this.sessions.get(this.currentEditingSession);
|
||||
if (chart) {
|
||||
chart.destroy();
|
||||
}
|
||||
this.sessions.delete(this.currentEditingSession);
|
||||
|
||||
// Limpiar refresh rate
|
||||
this.refreshRates.delete(this.currentEditingSession);
|
||||
}
|
||||
|
||||
// 4. Crear nuevo plot desde cero
|
||||
response = await fetch('/api/plots', {
|
||||
method: 'POST',
|
||||
// Modo edición: actualizar configuración vía PUT sin eliminar la sesión
|
||||
const sessionIdToUpdate = this.currentEditingSession;
|
||||
response = await fetch(`/api/plots/${sessionIdToUpdate}/config`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
const deleteResult = await deleteResponse.json();
|
||||
const result = await response.json();
|
||||
if (result && result.success) {
|
||||
// Actualizar estado local y aplicar cambios al chart existente
|
||||
const sessionData = this.sessions.get(sessionIdToUpdate);
|
||||
if (sessionData) {
|
||||
sessionData.config = { ...(sessionData.config || {}), ...config };
|
||||
this.applyConfigToChart(sessionIdToUpdate, config);
|
||||
}
|
||||
|
||||
// Actualizar nombre del tab si cambió
|
||||
if (typeof tabManager !== 'undefined' && config.name) {
|
||||
tabManager.updatePlotTabName(sessionIdToUpdate, config.name);
|
||||
tabManager.switchSubTab(`plot-${sessionIdToUpdate}`);
|
||||
}
|
||||
|
||||
this.updatePlotStats(sessionIdToUpdate, sessionData ? sessionData.config : config);
|
||||
this.hidePlotForm();
|
||||
showNotification(result.message || 'Plot updated', 'success');
|
||||
return;
|
||||
} else {
|
||||
showNotification((result && result.error) || 'Failed to update plot', 'error');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Modo creación normal
|
||||
response = await fetch('/api/plots', {
|
||||
|
@ -1492,46 +1585,36 @@ class PlotManager {
|
|||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
sessionId = result.session_id;
|
||||
|
||||
if (this.currentEditingSession) {
|
||||
showNotification(`Plot recreated successfully: ${config.name}`, 'success');
|
||||
} else {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
sessionId = result.session_id;
|
||||
showNotification(result.message, 'success');
|
||||
|
||||
// Crear nuevo tab para la sesión
|
||||
await this.createPlotSessionTab(sessionId, {
|
||||
name: config.name,
|
||||
variables_count: config.variables.length,
|
||||
trigger_enabled: config.trigger_enabled,
|
||||
trigger_variable: config.trigger_variable,
|
||||
trigger_on_true: config.trigger_on_true,
|
||||
is_active: false,
|
||||
is_paused: false
|
||||
});
|
||||
|
||||
// Cambiar al sub-tab del nuevo plot
|
||||
if (typeof tabManager !== 'undefined') {
|
||||
tabManager.switchSubTab(`plot-${sessionId}`);
|
||||
}
|
||||
|
||||
this.hidePlotForm();
|
||||
} else {
|
||||
showNotification(result.error, 'error');
|
||||
}
|
||||
|
||||
// Crear nuevo tab para la sesión (tanto en modo edición como creación)
|
||||
await this.createPlotSessionTab(sessionId, {
|
||||
name: config.name,
|
||||
variables_count: config.variables.length,
|
||||
trigger_enabled: config.trigger_enabled,
|
||||
trigger_variable: config.trigger_variable,
|
||||
trigger_on_true: config.trigger_on_true,
|
||||
is_active: false,
|
||||
is_paused: false
|
||||
});
|
||||
|
||||
// Cambiar al sub-tab del nuevo plot
|
||||
if (typeof tabManager !== 'undefined') {
|
||||
tabManager.switchSubTab(`plot-${sessionId}`);
|
||||
}
|
||||
|
||||
this.hidePlotForm();
|
||||
} else {
|
||||
showNotification(result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error submitting plot form:', error);
|
||||
if (this.currentEditingSession) {
|
||||
showNotification('Error recreating plot. Please try again.', 'error');
|
||||
} else {
|
||||
showNotification('Error creating plot', 'error');
|
||||
}
|
||||
showNotification(this.currentEditingSession ? 'Error updating plot' : 'Error creating plot', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1676,65 +1759,113 @@ class PlotManager {
|
|||
const sessionData = this.sessions.get(sessionId);
|
||||
if (!sessionData || !sessionData.chart) return;
|
||||
|
||||
// Guardar config local
|
||||
const chart = sessionData.chart;
|
||||
const wasPaused = sessionData.isPaused === true;
|
||||
|
||||
// Pausar ingesta/render en modo realtime para evitar condiciones de carrera
|
||||
try {
|
||||
if (sessionData.isRealTimeMode && chart.scales && chart.scales.x && chart.scales.x.realtime) {
|
||||
chart.scales.x.realtime.pause = true;
|
||||
}
|
||||
sessionData.ingestPaused = true;
|
||||
} catch (_) { /* noop */ }
|
||||
|
||||
// Guardar config local combinada
|
||||
sessionData.config = { ...(sessionData.config || {}), ...config };
|
||||
|
||||
// 1) Actualizar ventana de tiempo
|
||||
// 1) Detectar si cambian las variables/datasets (antes de tocar el chart)
|
||||
if (Array.isArray(config.variables)) {
|
||||
const previousLabels = (chart.data.datasets || []).map(ds => ds && ds.label).filter(Boolean);
|
||||
const changed = previousLabels.length !== config.variables.length ||
|
||||
previousLabels.some((lbl, idx) => lbl !== config.variables[idx]);
|
||||
|
||||
if (changed) {
|
||||
// Preservar refresh rate para la sesión
|
||||
const previousRefresh = this.refreshRates.get(sessionId) || 1000;
|
||||
|
||||
// Destruir chart actual para evitar estados inconsistentes del plugin
|
||||
try { chart.destroy(); } catch (_) { }
|
||||
|
||||
// Re-crear el chart con la nueva configuración sobre el mismo canvas
|
||||
this.createStreamingChart(sessionId, sessionData.config);
|
||||
|
||||
// Restaurar refresh rate y timers
|
||||
this.updateRefreshRate(sessionId, previousRefresh);
|
||||
|
||||
// Reaplicar estado de pausa/ingesta sobre el nuevo chart
|
||||
const newSessionData = this.sessions.get(sessionId);
|
||||
if (newSessionData) {
|
||||
newSessionData.isPaused = wasPaused;
|
||||
newSessionData.ingestPaused = wasPaused;
|
||||
try {
|
||||
if (newSessionData.isRealTimeMode && newSessionData.chart && newSessionData.chart.scales && newSessionData.chart.scales.x && newSessionData.chart.scales.x.realtime) {
|
||||
newSessionData.chart.scales.x.realtime.pause = wasPaused;
|
||||
} else if (!newSessionData.isRealTimeMode && !wasPaused) {
|
||||
this.startManualRefresh(sessionId);
|
||||
}
|
||||
} catch (_) { /* noop */ }
|
||||
}
|
||||
|
||||
// Y-axis range ya fue aplicado por createStreamingChart a partir de config
|
||||
return;
|
||||
} else {
|
||||
// Si no cambió el conjunto de variables, solo refrescar estilos
|
||||
const newDatasets = config.variables.map((variable, index) => {
|
||||
const color = this.getColor(variable, index);
|
||||
const existing = chart.data.datasets[index];
|
||||
if (existing) {
|
||||
existing.label = variable;
|
||||
existing.borderColor = color;
|
||||
existing.backgroundColor = color + '20';
|
||||
existing.borderWidth = 2;
|
||||
existing.fill = false;
|
||||
existing.spanGaps = true;
|
||||
existing.pointRadius = 0;
|
||||
existing.pointHoverRadius = 3;
|
||||
existing.tension = 0.1;
|
||||
return existing;
|
||||
}
|
||||
return {
|
||||
label: variable,
|
||||
data: [],
|
||||
borderColor: color,
|
||||
backgroundColor: color + '20',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
spanGaps: true,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 3,
|
||||
tension: 0.1
|
||||
};
|
||||
});
|
||||
chart.data.datasets = newDatasets;
|
||||
|
||||
// Recalcular índices y timestamps de empuje
|
||||
sessionData.datasetIndex = new Map();
|
||||
sessionData.lastPushedXByDataset = new Map();
|
||||
config.variables.forEach((variable, idx) => sessionData.datasetIndex.set(variable, idx));
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Actualizar ventana de tiempo y rango Y cuando no hubo recreación
|
||||
if (typeof config.time_window !== 'undefined') {
|
||||
this.updateTimeWindow(sessionId, config.time_window);
|
||||
}
|
||||
|
||||
// 2) Actualizar rango Y
|
||||
const chart = sessionData.chart;
|
||||
if (chart.options && chart.options.scales && chart.options.scales.y) {
|
||||
chart.options.scales.y.min = config.y_min ?? undefined;
|
||||
chart.options.scales.y.max = config.y_max ?? undefined;
|
||||
}
|
||||
|
||||
// 3) Actualizar datasets según variables
|
||||
if (Array.isArray(config.variables)) {
|
||||
const oldDatasets = chart.data.datasets || [];
|
||||
const oldByLabel = new Map();
|
||||
oldDatasets.forEach(ds => {
|
||||
if (ds && typeof ds.label === 'string') oldByLabel.set(ds.label, ds);
|
||||
});
|
||||
try { chart.update('quiet'); } catch (_) { }
|
||||
|
||||
const newDatasets = config.variables.map((variable, index) => {
|
||||
const existing = oldByLabel.get(variable);
|
||||
const color = this.getColor(variable, index);
|
||||
if (existing) {
|
||||
existing.label = variable;
|
||||
existing.borderColor = color;
|
||||
existing.backgroundColor = color + '20';
|
||||
existing.borderWidth = 2;
|
||||
existing.fill = false;
|
||||
existing.pointRadius = 0;
|
||||
existing.pointHoverRadius = 3;
|
||||
existing.tension = 0.1;
|
||||
return existing;
|
||||
}
|
||||
return {
|
||||
label: variable,
|
||||
data: [],
|
||||
borderColor: color,
|
||||
backgroundColor: color + '20',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 3,
|
||||
tension: 0.1
|
||||
};
|
||||
});
|
||||
|
||||
chart.data.datasets = newDatasets;
|
||||
|
||||
// Recalcular índices y timestamps de empuje
|
||||
sessionData.datasetIndex = new Map();
|
||||
sessionData.lastPushedXByDataset = new Map();
|
||||
config.variables.forEach((variable, idx) => sessionData.datasetIndex.set(variable, idx));
|
||||
}
|
||||
|
||||
chart.update('quiet');
|
||||
// 3) Reanudar ingesta/render si no estaba en pausa antes
|
||||
try {
|
||||
sessionData.ingestPaused = wasPaused;
|
||||
sessionData.isPaused = wasPaused;
|
||||
if (sessionData.isRealTimeMode && chart.scales && chart.scales.x && chart.scales.x.realtime) {
|
||||
chart.scales.x.realtime.pause = wasPaused;
|
||||
}
|
||||
} catch (_) { /* noop */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
"should_connect": true,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"DAR"
|
||||
"DAR",
|
||||
"Fast"
|
||||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-09T01:04:33.338067"
|
||||
"last_update": "2025-08-10T00:37:18.130615"
|
||||
}
|
Loading…
Reference in New Issue