diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 47e823f..a16bd3d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,5 +1,11 @@ # PLC S7-315 Streamer & Logger - AI Coding Guide +## Workingflow + +I m usign npm run dev so there is no need to build react +Also restart flask every time there is any modification to reset the application +So for testing the app now is running on http://localhost:5173/app with vite doing proxy + ## Architecture Overview This is a **dual-stack industrial automation system** for Siemens S7-315 PLCs combining Python backend orchestration with React frontend controls: diff --git a/application_events.json b/application_events.json index e68c088..442d157 100644 --- a/application_events.json +++ b/application_events.json @@ -1,750 +1,5 @@ { "events": [ - { - "timestamp": "2025-07-17T16:08:42.524816", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:08:42.527387", - "level": "info", - "event_type": "csv_started", - "message": "CSV recording started for 4 variables", - "details": { - "variables_count": 4, - "output_directory": "records\\17-07-2025" - } - }, - { - "timestamp": "2025-07-17T16:08:42.529631", - "level": "info", - "event_type": "streaming_started", - "message": "Streaming started with 4 variables", - "details": { - "variables_count": 4, - "streaming_variables_count": 4, - "sampling_interval": 0.1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T16:15:14.523187", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:15:14.534443", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:15:14.536431", - "level": "info", - "event_type": "csv_started", - "message": "CSV recording started for 4 variables", - "details": { - "variables_count": 4, - "output_directory": "records\\17-07-2025" - } - }, - { - "timestamp": "2025-07-17T16:15:14.538424", - "level": "info", - "event_type": "streaming_started", - "message": "Streaming started with 4 variables", - "details": { - "variables_count": 4, - "streaming_variables_count": 4, - "sampling_interval": 0.1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T16:15:43.961033", - "level": "info", - "event_type": "variable_added", - "message": "Variable added: PEW302 -> PEW302 (real)", - "details": { - "name": "PEW302", - "area": "pew", - "db": null, - "offset": 302, - "bit": null, - "type": "real", - "total_variables": 5 - } - }, - { - "timestamp": "2025-07-17T16:15:43.965019", - "level": "info", - "event_type": "csv_file_created", - "message": "New CSV file created after variable modification: _16_15_43.csv", - "details": { - "file_path": "records\\17-07-2025\\_16_15_43.csv", - "variables_count": 5, - "reason": "variable_modification" - } - }, - { - "timestamp": "2025-07-17T16:16:05.447969", - "level": "info", - "event_type": "variable_removed", - "message": "Variable removed: PEW302", - "details": { - "name": "PEW302", - "removed_config": { - "area": "pew", - "offset": 302, - "type": "real", - "streaming": false - }, - "total_variables": 4 - } - }, - { - "timestamp": "2025-07-17T16:16:05.452456", - "level": "info", - "event_type": "csv_file_created", - "message": "New CSV file created after variable modification: _16_16_05.csv", - "details": { - "file_path": "records\\17-07-2025\\_16_16_05.csv", - "variables_count": 4, - "reason": "variable_modification" - } - }, - { - "timestamp": "2025-07-17T16:16:05.460154", - "level": "error", - "event_type": "streaming_error", - "message": "Error in streaming loop: dictionary changed size during iteration", - "details": { - "error": "dictionary changed size during iteration", - "consecutive_errors": 1 - } - }, - { - "timestamp": "2025-07-17T16:16:21.389136", - "level": "info", - "event_type": "variable_added", - "message": "Variable added: PEW302 -> PEW302 (word)", - "details": { - "name": "PEW302", - "area": "pew", - "db": null, - "offset": 302, - "bit": null, - "type": "word", - "total_variables": 5 - } - }, - { - "timestamp": "2025-07-17T16:16:21.395123", - "level": "info", - "event_type": "csv_file_created", - "message": "New CSV file created after variable modification: _16_16_21.csv", - "details": { - "file_path": "records\\17-07-2025\\_16_16_21.csv", - "variables_count": 5, - "reason": "variable_modification" - } - }, - { - "timestamp": "2025-07-17T16:16:21.400619", - "level": "error", - "event_type": "streaming_error", - "message": "Error in streaming loop: dictionary changed size during iteration", - "details": { - "error": "dictionary changed size during iteration", - "consecutive_errors": 1 - } - }, - { - "timestamp": "2025-07-17T16:22:46.797675", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:22:46.830234", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:22:46.832226", - "level": "info", - "event_type": "csv_started", - "message": "CSV recording started for 5 variables", - "details": { - "variables_count": 5, - "output_directory": "records\\17-07-2025" - } - }, - { - "timestamp": "2025-07-17T16:22:46.834244", - "level": "info", - "event_type": "streaming_started", - "message": "Streaming started with 5 variables", - "details": { - "variables_count": 5, - "streaming_variables_count": 5, - "sampling_interval": 0.1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T16:26:24.716108", - "level": "info", - "event_type": "variable_added", - "message": "Variable added: PEW304 -> PEW304 (word)", - "details": { - "name": "PEW304", - "area": "pew", - "db": null, - "offset": 304, - "bit": null, - "type": "word", - "total_variables": 6 - } - }, - { - "timestamp": "2025-07-17T16:26:24.720753", - "level": "info", - "event_type": "csv_file_created", - "message": "New CSV file created after variable modification: _16_26_24.csv", - "details": { - "file_path": "records\\17-07-2025\\_16_26_24.csv", - "variables_count": 6, - "reason": "variable_modification" - } - }, - { - "timestamp": "2025-07-17T16:26:24.734873", - "level": "error", - "event_type": "streaming_error", - "message": "Error in streaming loop: dictionary changed size during iteration", - "details": { - "error": "dictionary changed size during iteration", - "consecutive_errors": 1 - } - }, - { - "timestamp": "2025-07-17T16:39:16.699525", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:39:16.726736", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:40:54.209555", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:40:54.239475", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:47:15.964360", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:47:15.987873", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:47:56.699662", - "level": "info", - "event_type": "dataset_created", - "message": "Dataset created: DAR (prefix: dar)", - "details": { - "dataset_id": "dar", - "name": "DAR", - "prefix": "dar", - "sampling_interval": 0.2 - } - }, - { - "timestamp": "2025-07-17T16:53:37.933620", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:53:37.955769", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:58:57.879859", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T16:58:57.906772", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T16:59:40.850574", - "level": "info", - "event_type": "variable_added", - "message": "Variable added to dataset 'DAR': PEW300 -> PEW300 (word)", - "details": { - "dataset_id": "dar", - "name": "PEW300", - "area": "pew", - "db": null, - "offset": 300, - "bit": null, - "type": "word", - "streaming": false - } - }, - { - "timestamp": "2025-07-17T17:00:22.254826", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 1, - "streaming_count": 1, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:00:22.255826", - "level": "info", - "event_type": "streaming_started", - "message": "Multi-dataset streaming started: 1 datasets activated", - "details": { - "activated_datasets": 1, - "total_datasets": 1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T17:00:27.051911", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 1, - "streaming_count": 1, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:00:31.772526", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 1, - "streaming_count": 1, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:00:31.773789", - "level": "info", - "event_type": "streaming_started", - "message": "Multi-dataset streaming started: 1 datasets activated", - "details": { - "activated_datasets": 1, - "total_datasets": 1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T17:01:02.407689", - "level": "info", - "event_type": "variable_added", - "message": "Variable added to dataset 'DAR': PEW302 -> PEW302 (word)", - "details": { - "dataset_id": "dar", - "name": "PEW302", - "area": "pew", - "db": null, - "offset": 302, - "bit": null, - "type": "word", - "streaming": false - } - }, - { - "timestamp": "2025-07-17T17:05:07.834680", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T17:05:07.863377", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T17:05:07.866355", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 2, - "streaming_count": 1, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:05:40.811603", - "level": "info", - "event_type": "variable_added", - "message": "Variable added to dataset 'DAR': UR62_Brix -> DB2122.18 (real)", - "details": { - "dataset_id": "dar", - "name": "UR62_Brix", - "area": "db", - "db": 2122, - "offset": 18, - "bit": null, - "type": "real", - "streaming": false - } - }, - { - "timestamp": "2025-07-17T17:05:40.813938", - "level": "error", - "event_type": "dataset_streaming_error", - "message": "Error in dataset 'DAR' streaming loop: dictionary changed size during iteration", - "details": { - "dataset_id": "dar", - "error": "dictionary changed size during iteration", - "consecutive_errors": 1 - } - }, - { - "timestamp": "2025-07-17T17:06:01.176474", - "level": "info", - "event_type": "variable_added", - "message": "Variable added to dataset 'DAR': UR29_Brix_Digital -> DB2120.40 (real)", - "details": { - "dataset_id": "dar", - "name": "UR29_Brix_Digital", - "area": "db", - "db": 2120, - "offset": 40, - "bit": null, - "type": "real", - "streaming": false - } - }, - { - "timestamp": "2025-07-17T17:06:01.181459", - "level": "error", - "event_type": "dataset_streaming_error", - "message": "Error in dataset 'DAR' streaming loop: dictionary changed size during iteration", - "details": { - "dataset_id": "dar", - "error": "dictionary changed size during iteration", - "consecutive_errors": 1 - } - }, - { - "timestamp": "2025-07-17T17:06:14.625201", - "level": "info", - "event_type": "variable_added", - "message": "Variable added to dataset 'DAR': CTS306_Conditi -> DB2124.18 (real)", - "details": { - "dataset_id": "dar", - "name": "CTS306_Conditi", - "area": "db", - "db": 2124, - "offset": 18, - "bit": null, - "type": "real", - "streaming": false - } - }, - { - "timestamp": "2025-07-17T17:06:46.809373", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 5, - "streaming_count": 4, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:06:46.813353", - "level": "info", - "event_type": "streaming_started", - "message": "Multi-dataset streaming started: 1 datasets activated", - "details": { - "activated_datasets": 1, - "total_datasets": 1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T17:06:48.330167", - "level": "info", - "event_type": "dataset_deactivated", - "message": "Dataset deactivated: DAR", - "details": { - "dataset_id": "dar" - } - }, - { - "timestamp": "2025-07-17T17:06:48.332439", - "level": "info", - "event_type": "streaming_stopped", - "message": "Multi-dataset streaming stopped: 1 datasets deactivated", - "details": {} - }, - { - "timestamp": "2025-07-17T17:06:49.786602", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 5, - "streaming_count": 4, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:06:49.788594", - "level": "info", - "event_type": "streaming_started", - "message": "Multi-dataset streaming started: 1 datasets activated", - "details": { - "activated_datasets": 1, - "total_datasets": 1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T17:11:56.050280", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T17:11:56.069205", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T17:11:56.074189", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 5, - "streaming_count": 4, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:12:22.650201", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 5, - "streaming_count": 4, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:12:22.653189", - "level": "info", - "event_type": "streaming_started", - "message": "Multi-dataset streaming started: 1 datasets activated", - "details": { - "activated_datasets": 1, - "total_datasets": 1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T17:14:13.134197", - "level": "info", - "event_type": "Application started", - "message": "Application initialization completed successfully", - "details": {} - }, - { - "timestamp": "2025-07-17T17:14:13.166983", - "level": "info", - "event_type": "plc_connection", - "message": "Successfully connected to PLC 10.1.33.11", - "details": { - "ip": "10.1.33.11", - "rack": 0, - "slot": 2 - } - }, - { - "timestamp": "2025-07-17T17:14:13.170485", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 5, - "streaming_count": 4, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:14:45.393113", - "level": "info", - "event_type": "dataset_activated", - "message": "Dataset activated: DAR", - "details": { - "dataset_id": "dar", - "variables_count": 5, - "streaming_count": 4, - "prefix": "dar" - } - }, - { - "timestamp": "2025-07-17T17:14:45.398362", - "level": "info", - "event_type": "streaming_started", - "message": "Multi-dataset streaming started: 1 datasets activated", - "details": { - "activated_datasets": 1, - "total_datasets": 1, - "udp_host": "127.0.0.1", - "udp_port": 9870 - } - }, - { - "timestamp": "2025-07-17T17:42:25.446579", - "level": "info", - "event_type": "dataset_csv_file_created", - "message": "New CSV file created after variable modification for dataset 'DAR': dar_17_42_25.csv", - "details": { - "dataset_id": "dar", - "file_path": "records\\17-07-2025\\dar_17_42_25.csv", - "variables_count": 4, - "reason": "variable_modification" - } - }, - { - "timestamp": "2025-07-17T17:42:25.450575", - "level": "info", - "event_type": "variable_removed", - "message": "Variable removed from dataset 'DAR': PEW302", - "details": { - "dataset_id": "dar", - "name": "PEW302", - "removed_config": { - "area": "pew", - "offset": 302, - "type": "word", - "streaming": false - } - } - }, { "timestamp": "2025-07-17T17:42:25.463541", "level": "error", @@ -10417,8 +9672,757 @@ "auto_started_recording": false, "recording_datasets": 0 } + }, + { + "timestamp": "2025-08-14T00:21:30.969939", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T00:25:10.966138", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T00:25:38.137588", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T00:25:38.151655", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T00:25:38.167408", + "level": "info", + "event_type": "plc_connection", + "message": "Successfully connected to PLC 10.1.33.11 and auto-started CSV recording for 3 datasets", + "details": { + "ip": "10.1.33.11", + "rack": 0, + "slot": 2, + "auto_started_recording": true, + "recording_datasets": 3, + "dataset_names": [ + "test", + "DAR", + "Fast" + ] + } + }, + { + "timestamp": "2025-08-14T00:25:38.215071", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T10:43:06.388133", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T10:43:12.535705", + "level": "error", + "event_type": "plc_connection_failed", + "message": "Failed to connect to PLC 10.1.33.11", + "details": { + "ip": "10.1.33.11", + "rack": 0, + "slot": 2, + "error": "b' ISO : An error occurred during recv TCP : Connection timed out'" + } + }, + { + "timestamp": "2025-08-14T10:44:08.250539", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T10:47:17.108715", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T10:47:17.129720", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T10:47:17.211575", + "level": "info", + "event_type": "plc_connection", + "message": "Successfully connected to PLC 10.1.33.11 and auto-started CSV recording for 3 datasets", + "details": { + "ip": "10.1.33.11", + "rack": 0, + "slot": 2, + "auto_started_recording": true, + "recording_datasets": 3, + "dataset_names": [ + "DAR", + "test", + "Fast" + ] + } + }, + { + "timestamp": "2025-08-14T10:47:17.232864", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T10:47:43.273690", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T10:49:51.663916", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_3", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T10:50:03.770716", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T10:50:03.820205", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T10:50:03.832879", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T10:50:03.869762", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T10:50:21.936785", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T10:53:04.260841", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T10:53:04.309239", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T10:53:04.323311", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T10:53:04.356487", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T10:54:06.213967", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T10:55:06.114506", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T10:55:06.181323", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T10:55:06.194323", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T10:55:06.243169", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T10:55:15.073091", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:00:00.117302", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T11:00:31.982807", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_3", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:00:48.531399", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T11:00:48.597287", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T11:00:48.618286", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T11:00:48.658954", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T11:00:55.550898", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:03:47.123660", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T11:03:47.204455", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T11:03:47.227624", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T11:03:47.300278", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T11:03:54.419601", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:04:17.413831", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_3", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:06:47.098482", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T11:06:47.180954", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T11:06:47.212147", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T11:06:47.242717", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T11:06:54.204399", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:09:54.002899", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T11:09:54.068911", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T11:09:54.087909", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T11:09:54.147764", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T11:10:01.164054", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:10:12.158544", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_3", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:10:29.318920", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_4", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:10:35.278563", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_5", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:10:38.404201", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_6", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 75, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:11:48.955386", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_7", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 25, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:11:51.255320", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_8", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 25, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:11:59.515081", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_9", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 25, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:14:46.634357", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T11:14:46.713183", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T11:14:46.724181", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T11:14:46.762615", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T11:15:03.411332", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_2", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 25, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T11:15:09.838725", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_3", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 25, + "trigger_variable": null, + "auto_started": true + } } ], - "last_updated": "2025-08-14T00:03:39.391445", + "last_updated": "2025-08-14T11:15:09.838725", "total_entries": 1000 } \ No newline at end of file diff --git a/config/data/dataset_definitions.json b/config/data/dataset_definitions.json index 0226d9a..7dc0ba0 100644 --- a/config/data/dataset_definitions.json +++ b/config/data/dataset_definitions.json @@ -6,7 +6,7 @@ "id": "DAR", "name": "DAR", "prefix": "gateway_phoenix", - "sampling_interval": 1.01 + "sampling_interval": 0.5 }, { "created": "2025-08-09T02:06:26.840011", diff --git a/config/data/plot_definitions.json b/config/data/plot_definitions.json index 120a5bf..98f3bfd 100644 --- a/config/data/plot_definitions.json +++ b/config/data/plot_definitions.json @@ -4,12 +4,20 @@ "id": "plot_1", "name": "UR29", "session_id": "plot_1", - "time_window": 75, + "time_window": 25, "trigger_enabled": false, "trigger_on_true": true, "trigger_variable": null, "y_max": null, "y_min": null + }, + { + "id": "Brix", + "name": "Brix", + "session_id": "Brix", + "time_window": 60, + "trigger_enabled": false, + "trigger_on_true": true } ] } \ No newline at end of file diff --git a/config/data/plot_variables.json b/config/data/plot_variables.json index 30ae53c..01ac26b 100644 --- a/config/data/plot_variables.json +++ b/config/data/plot_variables.json @@ -12,16 +12,6 @@ "color": "#e74c3c", "enabled": true, "variable_name": "UR29_ma" - }, - { - "color": "#2ecc71", - "enabled": true, - "variable_name": "fUR29_Brix" - }, - { - "color": "#f39c12", - "enabled": true, - "variable_name": "fUR29_ma" } ] } diff --git a/core/config_manager.py b/core/config_manager.py index 0e9e7da..27fb961 100644 --- a/core/config_manager.py +++ b/core/config_manager.py @@ -173,40 +173,81 @@ class ConfigManager: self.logger.error(f"Error loading datasets: {e}") def _load_datasets_separated(self): - """Load datasets from separated definition and variable files""" + """Load datasets from separated definition and variable files (new array format)""" try: - # Load definitions + # Load definitions (new array format: {"datasets": [array]}) with open(self.dataset_definitions_file, "r") as f: definitions_data = json.load(f) - # Load variables + # Load variables (new array format: {"variables": [array]}) with open(self.dataset_variables_file, "r") as f: variables_data = json.load(f) - # Merge data back to legacy format for compatibility + # Convert new array format to internal dictionary format for compatibility self.datasets = {} - dataset_defs = definitions_data.get("datasets", {}) - dataset_vars = variables_data.get("dataset_variables", {}) - for dataset_id, definition in dataset_defs.items(): - variables_info = dataset_vars.get(dataset_id, {}) - self.datasets[dataset_id] = { - **definition, - "variables": variables_info.get("variables", {}), - "streaming_variables": variables_info.get( - "streaming_variables", [] - ), - } + # Process dataset definitions array + dataset_defs_array = definitions_data.get("datasets", []) + for definition in dataset_defs_array: + dataset_id = definition.get("id") + if not dataset_id: + if self.logger: + self.logger.warning("Skipping dataset definition without id") + continue + + # Store definition without the id field (since id is the key) + dataset_def = {k: v for k, v in definition.items() if k != "id"} + self.datasets[dataset_id] = dataset_def + + # Process dataset variables array and merge with definitions + dataset_vars_array = variables_data.get("variables", []) + for variables_info in dataset_vars_array: + dataset_id = variables_info.get("dataset_id") + if not dataset_id: + if self.logger: + self.logger.warning( + "Skipping dataset variables without dataset_id" + ) + continue + + if dataset_id not in self.datasets: + if self.logger: + self.logger.warning( + f"Found variables for unknown dataset: {dataset_id}" + ) + continue + + # Convert variables array to dictionary format for internal use + variables_list = variables_info.get("variables", []) + variables_dict = {} + streaming_variables = [] + + for var in variables_list: + var_name = var.get("name") + if not var_name: + continue + + # Build variable config (remove name since it's the key) + var_config = {k: v for k, v in var.items() if k != "name"} + variables_dict[var_name] = var_config + + # Add to streaming list if enabled + if var_config.get("streaming", False): + streaming_variables.append(var_name) + + # Add variables to dataset + self.datasets[dataset_id]["variables"] = variables_dict + self.datasets[dataset_id]["streaming_variables"] = streaming_variables # Calculate active_datasets automatically from enabled field self.active_datasets = set() - for dataset_id, definition in dataset_defs.items(): + for dataset_id, definition in self.datasets.items(): if definition.get("enabled", False): self.active_datasets.add(dataset_id) # current_dataset_id is optional for UI, use first available if not set - self.current_dataset_id = definitions_data.get("current_dataset_id") - if not self.current_dataset_id and self.datasets: + self.current_dataset_id = None + if self.datasets: self.current_dataset_id = next(iter(self.datasets.keys())) if self.logger: @@ -230,8 +271,7 @@ class ConfigManager: self.active_datasets = set(legacy_data.get("active_datasets", [])) self.current_dataset_id = legacy_data.get("current_dataset_id") - # Save to new separated format - self.save_datasets() + # Note: Migration complete - data now managed by frontend via RJSF if self.logger: self.logger.info( @@ -244,53 +284,9 @@ class ConfigManager: self.logger.error(f"Error migrating legacy datasets: {e}") raise - def save_datasets(self): - """Save datasets configuration to separated JSON files""" - try: - # timestamp removed as we don't save static fields anymore - - # Prepare definitions data - only datasets, no static fields - definitions_data = { - "datasets": {}, - } - - # Prepare variables data - only variables, no static fields - variables_data = { - "dataset_variables": {}, - } - - # Split datasets into definitions and variables - for dataset_id, dataset_info in self.datasets.items(): - # Extract definition (metadata only) - definition = { - key: value - for key, value in dataset_info.items() - if key not in ["variables", "streaming_variables"] - } - definitions_data["datasets"][dataset_id] = definition - - # Extract variables - variables_data["dataset_variables"][dataset_id] = { - "variables": dataset_info.get("variables", {}), - "streaming_variables": dataset_info.get("streaming_variables", []), - } - - # Save both files - with open(self.dataset_definitions_file, "w") as f: - json.dump(definitions_data, f, indent=4) - - with open(self.dataset_variables_file, "w") as f: - json.dump(variables_data, f, indent=4) - - if self.logger: - self.logger.info( - f"Datasets configuration saved to separated files: " - f"{self.dataset_definitions_file} and {self.dataset_variables_file}" - ) - - except Exception as e: - if self.logger: - self.logger.error(f"Error saving datasets: {e}") + # DEPRECATED: save_datasets() method removed + # Data is now saved directly from frontend via RJSF and API endpoints + # Use load_datasets_separated() to reload configuration when needed def sync_streaming_variables(self): """Synchronize streaming variables configuration""" @@ -323,9 +319,12 @@ class ConfigManager: ) if sync_needed: - self.save_datasets() + # Note: Configuration is now managed by frontend via RJSF + # No automatic save needed - frontend will save when user makes changes if self.logger: - self.logger.info("Streaming variables configuration synchronized") + self.logger.info( + "Streaming variables configuration synchronized in memory" + ) except Exception as e: if self.logger: @@ -517,7 +516,7 @@ class ConfigManager: if not self.current_dataset_id: self.current_dataset_id = dataset_id - self.save_datasets() + # Note: Dataset changes now saved via frontend RJSF return new_dataset def delete_dataset(self, dataset_id: str): @@ -537,7 +536,7 @@ class ConfigManager: next(iter(self.datasets.keys())) if self.datasets else None ) - self.save_datasets() + # Note: Dataset deletion now saved via frontend RJSF return dataset_info def get_current_dataset(self): @@ -618,7 +617,7 @@ class ConfigManager: if name not in self.datasets[dataset_id]["streaming_variables"]: self.datasets[dataset_id]["streaming_variables"].append(name) - self.save_datasets() + # Note: Variable addition now saved via frontend RJSF return var_config def remove_variable_from_dataset(self, dataset_id: str, name: str): @@ -636,7 +635,7 @@ class ConfigManager: if name in self.datasets[dataset_id]["streaming_variables"]: self.datasets[dataset_id]["streaming_variables"].remove(name) - self.save_datasets() + # Note: Variable removal now saved via frontend RJSF return var_config def toggle_variable_streaming(self, dataset_id: str, name: str, enabled: bool): @@ -658,7 +657,7 @@ class ConfigManager: if name in self.datasets[dataset_id]["streaming_variables"]: self.datasets[dataset_id]["streaming_variables"].remove(name) - self.save_datasets() + # Note: Streaming toggle now saved via frontend RJSF def activate_dataset(self, dataset_id: str): """Mark a dataset as active""" @@ -667,7 +666,7 @@ class ConfigManager: self.datasets[dataset_id]["enabled"] = True self._update_active_datasets() - self.save_datasets() + # Note: Dataset activation now saved via frontend RJSF def deactivate_dataset(self, dataset_id: str): """Mark a dataset as inactive""" @@ -676,7 +675,7 @@ class ConfigManager: self.datasets[dataset_id]["enabled"] = False self._update_active_datasets() - self.save_datasets() + # Note: Dataset deactivation now saved via frontend RJSF def _update_active_datasets(self): """Update active_datasets based on enabled field of each dataset""" @@ -688,14 +687,15 @@ class ConfigManager: def get_status(self): """Get configuration status""" total_variables = sum( - len(dataset["variables"]) for dataset in self.datasets.values() + len(self.get_dataset_variables(dataset_id)) + for dataset_id in self.datasets.keys() ) # Count only variables that are in streaming_variables list AND have streaming=true total_streaming_vars = 0 - for dataset in self.datasets.values(): + for dataset_id, dataset in self.datasets.items(): streaming_vars = dataset.get("streaming_variables", []) - variables_config = dataset.get("variables", {}) + variables_config = self.get_dataset_variables(dataset_id) active_streaming_vars = [ var for var in streaming_vars @@ -717,12 +717,12 @@ class ConfigManager: dataset_id: { "name": info["name"], "prefix": info["prefix"], - "variables_count": len(info["variables"]), + "variables_count": len(self.get_dataset_variables(dataset_id)), "streaming_count": len( [ var for var in info.get("streaming_variables", []) - if info.get("variables", {}) + if self.get_dataset_variables(dataset_id) .get(var, {}) .get("streaming", False) ] diff --git a/core/plc_data_streamer.py b/core/plc_data_streamer.py index 38deb99..78e50de 100644 --- a/core/plc_data_streamer.py +++ b/core/plc_data_streamer.py @@ -366,8 +366,8 @@ class PLCDataStreamer: "datasets_count": len(self.config_manager.datasets), "active_datasets_count": len(self.config_manager.active_datasets), "total_variables": sum( - len(dataset["variables"]) - for dataset in self.config_manager.datasets.values() + len(self.config_manager.get_dataset_variables(dataset_id)) + for dataset_id in self.config_manager.datasets.keys() ), "streaming_variables_count": sum( len(dataset.get("streaming_variables", [])) @@ -586,7 +586,7 @@ class PLCDataStreamer: def current_dataset_id(self, value): """Set current dataset ID (backward compatibility)""" self.config_manager.current_dataset_id = value - self.config_manager.save_datasets() + # Note: Dataset changes now saved via frontend RJSF @property def connected(self): @@ -598,6 +598,6 @@ class PLCDataStreamer: """Get streaming status (backward compatibility)""" return self.data_streamer.is_streaming() - def save_datasets(self): - """Save datasets (backward compatibility)""" - self.config_manager.save_datasets() + # DEPRECATED: save_datasets() method removed + # Data is now saved directly from frontend via RJSF and API endpoints + # Use load_datasets() to reload configuration when needed diff --git a/core/plot_manager.py b/core/plot_manager.py index 05aa247..2233a55 100644 --- a/core/plot_manager.py +++ b/core/plot_manager.py @@ -294,8 +294,7 @@ class PlotManager: self.sessions[session_id] = session - # Guardar automáticamente la configuración - self.save_plots() + # Note: Plot session configuration now saved via frontend RJSF if self.logger: self.logger.info( @@ -338,8 +337,7 @@ class PlotManager: del self.sessions[session_id] - # Guardar automáticamente después de eliminar - self.save_plots() + # Note: Plot session removal now saved via frontend RJSF return True return False @@ -446,26 +444,57 @@ class PlotManager: self.session_counter = 0 def _load_plots_separated(self): - """Load plots from separated definition and variable files""" + """Load plots from separated definition and variable files (new array format)""" try: - # Load definitions + # Load definitions (new array format: {"plots": [array]}) with open(self.plot_definitions_file, "r", encoding="utf-8") as f: definitions_data = json.load(f) - # Load variables + # Load variables (new array format: {"variables": [array]}) with open(self.plot_variables_file, "r", encoding="utf-8") as f: variables_data = json.load(f) - # Merge data back for session creation - plots_data = definitions_data.get("plots", {}) - plot_vars = variables_data.get("plot_variables", {}) + # Convert new array format to internal dictionary format for compatibility + plots_array = definitions_data.get("plots", []) + plot_vars_array = variables_data.get("variables", []) - for session_id, plot_config in plots_data.items(): - # Add variables to config - variables_info = plot_vars.get(session_id, {}) + # Build plot variables lookup by plot_id + plot_variables_lookup = {} + for plot_vars_entry in plot_vars_array: + plot_id = plot_vars_entry.get("plot_id") + if plot_id: + # Convert variables array to format expected by PlotSession + variables_list = plot_vars_entry.get("variables", []) + # Convert to object format with variable names and properties + variables_config = {} + for var in variables_list: + var_name = var.get("variable_name") + if var_name: + variables_config[var_name] = { + "variable_name": var_name, + "color": var.get("color", "#3498db"), + "enabled": var.get("enabled", True), + } + plot_variables_lookup[plot_id] = variables_config + + # Process plot definitions + for plot_def in plots_array: + session_id = plot_def.get("id") or plot_def.get("session_id") + if not session_id: + if self.logger: + self.logger.warning("Skipping plot definition without id") + continue + + # Build full config with variables full_config = { - **plot_config, - "variables": variables_info.get("variables", []), + "name": plot_def.get("name", f"Plot {session_id}"), + "time_window": plot_def.get("time_window", 60), + "y_min": plot_def.get("y_min"), + "y_max": plot_def.get("y_max"), + "trigger_variable": plot_def.get("trigger_variable"), + "trigger_enabled": plot_def.get("trigger_enabled", False), + "trigger_on_true": plot_def.get("trigger_on_true", True), + "variables": plot_variables_lookup.get(session_id, {}), } # Create session with full configuration @@ -478,16 +507,16 @@ class PlotManager: # Update counter to avoid duplicate IDs try: - session_num = int(session_id.split("_")[1]) - if session_num >= self.session_counter: - self.session_counter = session_num + 1 + if session_id.startswith("plot_"): + session_num = int(session_id.split("_")[1]) + if session_num >= self.session_counter: + self.session_counter = session_num + 1 except (IndexError, ValueError): pass - # Load counter from definitions - saved_counter = definitions_data.get("session_counter", len(self.sessions)) - if saved_counter > self.session_counter: - self.session_counter = saved_counter + # Ensure session counter is at least the number of sessions + if len(self.sessions) >= self.session_counter: + self.session_counter = len(self.sessions) if self.logger and self.sessions: self.logger.info( @@ -527,8 +556,7 @@ class PlotManager: if saved_counter > self.session_counter: self.session_counter = saved_counter - # Save to new separated format - self.save_plots() + # Note: Migration complete - data now managed by frontend via RJSF if self.logger: self.logger.info( @@ -540,53 +568,9 @@ class PlotManager: self.logger.error(f"Error migrating legacy plots: {e}") raise - def save_plots(self): - """Guardar plots a archivos separados de persistencia""" - try: - # Prepare definitions data - only plots, no static fields - definitions_data = { - "plots": {}, - } - - # Prepare variables data - only variables, no static fields - variables_data = { - "plot_variables": {}, - } - - # Split sessions into definitions and variables - for session_id, session in self.sessions.items(): - # Extract definition (metadata without variables) - definitions_data["plots"][session_id] = { - "name": session.name, - "time_window": session.time_window, - "y_min": session.y_min, - "y_max": session.y_max, - "trigger_variable": session.trigger_variable, - "trigger_enabled": session.trigger_enabled, - "trigger_on_true": session.trigger_on_true, - "session_id": session_id, - } - - # Extract variables - variables_data["plot_variables"][session_id] = { - "variables": session.variables, - } - - # Save both files - with open(self.plot_definitions_file, "w", encoding="utf-8") as f: - json.dump(definitions_data, f, indent=2, ensure_ascii=False) - - with open(self.plot_variables_file, "w", encoding="utf-8") as f: - json.dump(variables_data, f, indent=2, ensure_ascii=False) - - if self.logger: - self.logger.debug( - f"Saved {len(self.sessions)} plot sessions to separated files" - ) - - except Exception as e: - if self.logger: - self.logger.error(f"Error saving plot sessions: {e}") + # DEPRECATED: save_plots() method removed + # Data is now saved directly from frontend via RJSF and API endpoints + # Use _load_plots_separated() to reload configuration when needed def update_session_config(self, session_id: str, config: Dict[str, Any]) -> bool: """Actualizar configuración de una sesión existente""" @@ -632,8 +616,7 @@ class PlotManager: old_data = list(session.data[var]) session.data[var] = deque(old_data, maxlen=max_points) - # Guardar cambios - self.save_plots() + # Note: Plot session configuration changes now saved via frontend RJSF if self.logger: self.logger.info(f"Updated plot session '{session.name}' configuration") diff --git a/core/schema_manager.py b/core/schema_manager.py index cdba108..14a507c 100644 --- a/core/schema_manager.py +++ b/core/schema_manager.py @@ -315,7 +315,7 @@ class ConfigSchemaManager: if current and current in datasets: self.config_manager.current_dataset_id = current - self.config_manager.save_datasets() + # Note: Data is now persisted directly via frontend RJSF return {"success": True} if config_id == "plot-definitions": @@ -342,8 +342,7 @@ class ConfigSchemaManager: else: self.plot_manager.session_counter = 0 - # Save to separated files - self.plot_manager.save_plots() + # Note: Data is now persisted directly via frontend RJSF except Exception as e: if self.logger: @@ -376,9 +375,11 @@ class ConfigSchemaManager: with open(path, "w", encoding="utf-8") as f: json.dump(self.read_config("plc"), f, indent=2) elif config_id == "datasets": - self.config_manager.save_datasets() # Now saves to separated files + # Note: Datasets now managed via separated files by frontend RJSF + pass elif config_id == "plots": - self.plot_manager.save_plots() # Now saves to separated files + # Note: Plots now managed via separated files by frontend RJSF + pass elif config_id in [ "dataset-definitions", "dataset-variables", diff --git a/core/streamer.py b/core/streamer.py index 6902dc7..2d81ecb 100644 --- a/core/streamer.py +++ b/core/streamer.py @@ -683,7 +683,9 @@ class DataStreamer: f"Dataset activated: {dataset_info['name']}", { "dataset_id": dataset_id, - "variables_count": len(dataset_info["variables"]), + "variables_count": len( + self.config_manager.get_dataset_variables(dataset_id) + ), "streaming_count": len(dataset_info["streaming_variables"]), "prefix": dataset_info["prefix"], }, diff --git a/frontend/src/components/ChartjsPlot.jsx b/frontend/src/components/ChartjsPlot.jsx index 2dfbebf..afd3fc8 100644 --- a/frontend/src/components/ChartjsPlot.jsx +++ b/frontend/src/components/ChartjsPlot.jsx @@ -15,13 +15,14 @@ const ChartjsPlot = ({ session, height = '400px' }) => { isRealTimeMode: true, refreshRate: 1000, userOverrideUntil: 0, - userPaused: false + userPaused: false, + sessionId: null }); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [dataPointsCount, setDataPointsCount] = useState(0); - const [resolvedConfig, setResolvedConfig] = useState(null); + const resolvedConfigRef = useRef(null); const bgColor = useColorModeValue('white', 'gray.800'); const textColor = useColorModeValue('gray.600', 'gray.300'); @@ -55,6 +56,17 @@ const ChartjsPlot = ({ session, height = '400px' }) => { if (!variables) return []; if (Array.isArray(variables)) { + // Handle array of objects with variable_name property + if (variables.length > 0 && typeof variables[0] === 'object' && variables[0].variable_name) { + return variables + .filter(varConfig => varConfig.enabled !== false && varConfig.variable_name) + .map((varConfig, index) => ({ + name: varConfig.variable_name, + color: varConfig.color || getColor(varConfig.variable_name, index), + enabled: varConfig.enabled !== false + })); + } + // Handle simple array of strings return variables.map((variable, index) => ({ name: variable, color: getColor(variable, index), @@ -84,9 +96,11 @@ const ChartjsPlot = ({ session, height = '400px' }) => { }, [getColor]); const createStreamingChart = useCallback(async () => { - const cfg = resolvedConfig || session?.config; + const cfg = resolvedConfigRef.current || session?.config; if (!canvasRef.current || !cfg) return; + console.log(`🔧 Creating chart for session ${session?.session_id}...`); + try { // Ensure Chart.js and plugins are loaded if (typeof window.Chart === 'undefined') { @@ -246,10 +260,10 @@ const ChartjsPlot = ({ session, height = '400px' }) => { sessionDataRef.current.isRealTimeMode = true; sessionDataRef.current.noDataCycles = 0; // Sync ingest pause state with initial chart pause - const initialPaused = !session.is_active || session.is_paused; + const initialPaused = !session?.is_active || session?.is_paused; sessionDataRef.current.ingestPaused = initialPaused; sessionDataRef.current.isPaused = initialPaused; - console.log(`✅ Plot ${session.session_id}: Real-time Streaming enabled`); + console.log(`✅ Plot ${session?.session_id}: Real-time Streaming enabled`); setIsLoading(false); setError(null); @@ -259,10 +273,11 @@ const ChartjsPlot = ({ session, height = '400px' }) => { setError(error.message); setIsLoading(false); } - }, [session, resolvedConfig, getEnabledVariables, getColor]); + }, []); const onStreamingRefresh = useCallback(async (chart) => { - if (!session?.session_id) return; + const sessionId = sessionDataRef.current.sessionId; + if (!sessionId) return; try { const now = Date.now(); @@ -276,14 +291,18 @@ const ChartjsPlot = ({ session, height = '400px' }) => { sessionDataRef.current.lastDataFetch = now; // Fetch data from backend - const response = await fetch(`/api/plots/${session.session_id}/data`); + const response = await fetch(`/api/plots/${sessionId}/data`); if (!response.ok) return; const plotData = await response.json(); - + // Add new data to chart const pointsAdded = addNewDataToStreaming(plotData, now); updatePointsCounter(plotData); + + if (pointsAdded > 0) { + console.log(`📊 Plot ${sessionId}: Added ${pointsAdded} points to chart`); + } // Auto-pause when no data arrives for several cycles; resume when data appears if (pointsAdded > 0) { @@ -308,9 +327,9 @@ const ChartjsPlot = ({ session, height = '400px' }) => { } } catch (error) { - console.error(`📈 Error in streaming refresh for ${session.session_id}:`, error); + console.error(`📈 Error in streaming refresh for ${sessionDataRef.current.sessionId}:`, error); } - }, [session?.session_id]); + }, []); const addNewDataToStreaming = useCallback((plotData, timestamp) => { if (!chartRef.current || !plotData) return 0; @@ -485,6 +504,9 @@ const ChartjsPlot = ({ session, height = '400px' }) => { // Also expose control functions through props for easier access React.useEffect(() => { + // Update sessionId ref when session changes + sessionDataRef.current.sessionId = session?.session_id || null; + if (typeof session?.onChartReady === 'function') { session.onChartReady({ pauseStreaming, @@ -492,7 +514,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { clearChart }); } - }, [pauseStreaming, resumeStreaming, clearChart, session]); + }, [pauseStreaming, resumeStreaming, clearChart, session?.session_id, session?.onChartReady]); // Update chart when session status changes useEffect(() => { @@ -510,53 +532,17 @@ const ChartjsPlot = ({ session, height = '400px' }) => { } }, [session?.is_active, session?.is_paused, pauseStreaming, resumeStreaming]); - // Resolve config: use provided session.config or fetch from backend + // Initialize chart when config is resolved - simplified approach useEffect(() => { - let cancelled = false; - async function resolveConfig() { - try { - setIsLoading(true); - setError(null); - // If config already present in session, use it - if (session?.config) { - if (!cancelled) { - setResolvedConfig(session.config); - setIsLoading(false); - } - return; - } - // Otherwise fetch it from backend - if (session?.session_id) { - const resp = await fetch(`/api/plots/${session.session_id}/config`); - if (!resp.ok) { - const txt = await resp.text().catch(() => resp.statusText); - throw new Error(`HTTP ${resp.status}: ${txt || resp.statusText}`); - } - const data = await resp.json(); - if (data?.success && data?.config) { - if (!cancelled) setResolvedConfig(data.config); - } else { - throw new Error('Plot config not available'); - } - } else { - throw new Error('Invalid session (missing session_id)'); - } - } catch (e) { - if (!cancelled) setError(e.message || 'Error loading plot config'); - } finally { - if (!cancelled) setIsLoading(false); + // Only create chart once when we have a session_id and canvas + if (session?.session_id && canvasRef.current && !chartRef.current) { + const config = session?.config; + if (config) { + resolvedConfigRef.current = config; + createStreamingChart(); } } - - resolveConfig(); - return () => { cancelled = true; }; - }, [session?.session_id, session?.config]); - - // Initialize chart when config is resolved - useEffect(() => { - if (resolvedConfig) { - createStreamingChart(); - } + return () => { try { if (chartRef.current) { @@ -573,7 +559,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { clearInterval(sessionDataRef.current.manualInterval); } }; - }, [resolvedConfig, createStreamingChart]); + }, [session?.session_id]); if (isLoading) { return ( diff --git a/frontend/src/components/PlotRealtimeSession.jsx b/frontend/src/components/PlotRealtimeSession.jsx index 8ac75ec..1897d44 100644 --- a/frontend/src/components/PlotRealtimeSession.jsx +++ b/frontend/src/components/PlotRealtimeSession.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState, useCallback } from 'react' +import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react' import { Box, VStack, @@ -67,9 +67,13 @@ export default function PlotRealtimeSession({ const borderColor = useColorModeValue('gray.200', 'gray.600') const muted = useColorModeValue('gray.600', 'gray.300') - // Enhanced session object for ChartjsPlot - const enhancedSession = { - ...session, + // Enhanced session object for ChartjsPlot - memoized to prevent recreations + const enhancedSession = useMemo(() => ({ + session_id: plotDefinition.id, + name: plotDefinition.name, + is_active: session.is_active, + is_paused: session.is_paused, + variables_count: plotVariables.length, config: { ...plotDefinition, ...localConfig, @@ -78,7 +82,15 @@ export default function PlotRealtimeSession({ onChartReady: (controls) => { chartControlsRef.current = controls } - } + }), [ + plotDefinition.id, + plotDefinition.name, + plotDefinition, + session.is_active, + session.is_paused, + plotVariables, + localConfig + ]) // Load session status from backend (optional - session may not exist until started) const refreshSessionStatus = useCallback(async () => { @@ -132,29 +144,7 @@ export default function PlotRealtimeSession({ // Control plot session (start, pause, stop, clear) const handleControlClick = async (action) => { - // Apply immediate local feedback - if (chartControlsRef.current) { - switch (action) { - case 'pause': - chartControlsRef.current.pauseStreaming() - setSession(prev => ({ ...prev, is_paused: true })) - break - case 'start': - case 'resume': - chartControlsRef.current.resumeStreaming() - setSession(prev => ({ ...prev, is_active: true, is_paused: false })) - break - case 'clear': - chartControlsRef.current.clearChart() - break - case 'stop': - chartControlsRef.current.pauseStreaming() - setSession(prev => ({ ...prev, is_active: false, is_paused: false })) - break - } - } - - // Send command to backend + // Send command to backend first try { // For 'start' action, create the plot session first if it doesn't exist if (action === 'start') { @@ -177,9 +167,45 @@ export default function PlotRealtimeSession({ } } + // Send control command to backend await api.controlPlotSession(plotDefinition.id, action) - // Refresh status after backend command - setTimeout(refreshSessionStatus, 500) + + // For 'start' action, verify that the session is actually active + if (action === 'start') { + // Wait a bit and verify the session started + await new Promise(resolve => setTimeout(resolve, 300)) + const verifyResponse = await api.getPlotSession(plotDefinition.id) + if (!verifyResponse?.config?.is_active) { + // Try the control command once more if not active + console.log('Session not active, retrying control command...') + await api.controlPlotSession(plotDefinition.id, action) + } + } + + // Apply local feedback after successful backend response + if (chartControlsRef.current) { + switch (action) { + case 'pause': + chartControlsRef.current.pauseStreaming() + setSession(prev => ({ ...prev, is_paused: true })) + break + case 'start': + case 'resume': + chartControlsRef.current.resumeStreaming() + setSession(prev => ({ ...prev, is_active: true, is_paused: false })) + break + case 'clear': + chartControlsRef.current.clearChart() + break + case 'stop': + chartControlsRef.current.pauseStreaming() + setSession(prev => ({ ...prev, is_active: false, is_paused: false })) + break + } + } + + // Refresh status after backend command (shorter delay) + setTimeout(refreshSessionStatus, 200) } catch (error) { toast({ title: `❌ Failed to ${action} plot`, diff --git a/main.py b/main.py index f7b804d..513857b 100644 --- a/main.py +++ b/main.py @@ -1270,7 +1270,7 @@ def set_current_dataset(): if dataset_id and dataset_id in streamer.datasets: streamer.current_dataset_id = dataset_id - streamer.save_datasets() + # Note: No need to save - this is just changing current selection in memory return jsonify( { "success": True, diff --git a/system_state.json b/system_state.json index fcb5cd3..2b785b2 100644 --- a/system_state.json +++ b/system_state.json @@ -1,9 +1,13 @@ { "last_state": { - "should_connect": false, + "should_connect": true, "should_stream": false, - "active_datasets": [] + "active_datasets": [ + "Test", + "DAR", + "Fast" + ] }, "auto_recovery_enabled": true, - "last_update": "2025-08-13T14:54:09.753196" + "last_update": "2025-08-14T11:14:46.738038" } \ No newline at end of file