diff --git a/CHARTJS_STREAMING_INTEGRATION.md b/.doc/CHARTJS_STREAMING_INTEGRATION.md similarity index 100% rename from CHARTJS_STREAMING_INTEGRATION.md rename to .doc/CHARTJS_STREAMING_INTEGRATION.md diff --git a/CONSOLE_CLEANUP_SUMMARY.md b/.doc/CONSOLE_CLEANUP_SUMMARY.md similarity index 100% rename from CONSOLE_CLEANUP_SUMMARY.md rename to .doc/CONSOLE_CLEANUP_SUMMARY.md diff --git a/EJEMPLO_USO_PLOTTING.md b/.doc/EJEMPLO_USO_PLOTTING.md similarity index 100% rename from EJEMPLO_USO_PLOTTING.md rename to .doc/EJEMPLO_USO_PLOTTING.md diff --git a/.doc/MemoriaDeEvolucion.md b/.doc/MemoriaDeEvolucion.md index 1993869..4ce6700 100644 --- a/.doc/MemoriaDeEvolucion.md +++ b/.doc/MemoriaDeEvolucion.md @@ -1,3 +1,19 @@ +2025-08-11 - Favicon y logos + +- Resumen petición usuario: Cambiar el logo por `record.png` y que también se vea en la pestaña del navegador (favicon). + +- Cambios clave: + - `frontend/index.html`: `` y `shortcut icon` para forzar recarga. + - `main.py`: Ruta `/favicon.ico` que sirve `static/icons/record.png` para cubrir la petición automática del navegador. + - `frontend/src/App.jsx`: Reemplazo de logos visuales por `/static/icons/record.png` en header y navbar. + +- Decisiones/Notas: + - Estándar adoptado para estáticos en React: + - `frontend/public/`: archivos públicos sin import (favicon, robots.txt, imágenes públicas). Servidos desde la raíz: `/favicon.ico`, `/record.png`. + - `frontend/src/assets/`: assets usados por componentes React, importados con `import img from '...';` para que Vite haga hashing y optimización. + - Para favicon no usar archivos bajo `frontend/src/…`. Debe estar en `frontend/public` y enlazarse como `/favicon.ico`. + - Se añadió `?v=2` para evitar caché del navegador. + # Project Evolution Memory ## PLC S7-315 Streamer & Logger diff --git a/PLOTTING_FINALIZATION_SUMMARY.md b/.doc/PLOTTING_FINALIZATION_SUMMARY.md similarity index 100% rename from PLOTTING_FINALIZATION_SUMMARY.md rename to .doc/PLOTTING_FINALIZATION_SUMMARY.md diff --git a/PLOTTING_FIXES_FINAL.md b/.doc/PLOTTING_FIXES_FINAL.md similarity index 100% rename from PLOTTING_FIXES_FINAL.md rename to .doc/PLOTTING_FIXES_FINAL.md diff --git a/PLOTTING_FIXES_RESUMEN.md b/.doc/PLOTTING_FIXES_RESUMEN.md similarity index 100% rename from PLOTTING_FIXES_RESUMEN.md rename to .doc/PLOTTING_FIXES_RESUMEN.md diff --git a/PLOTTING_STATUS_FINAL.md b/.doc/PLOTTING_STATUS_FINAL.md similarity index 100% rename from PLOTTING_STATUS_FINAL.md rename to .doc/PLOTTING_STATUS_FINAL.md diff --git a/PLOTTING_SYSTEM.md b/.doc/PLOTTING_SYSTEM.md similarity index 100% rename from PLOTTING_SYSTEM.md rename to .doc/PLOTTING_SYSTEM.md diff --git a/README.md b/.doc/README.md similarity index 100% rename from README.md rename to .doc/README.md diff --git a/REAL_TIME_STREAMING.md b/.doc/REAL_TIME_STREAMING.md similarity index 100% rename from REAL_TIME_STREAMING.md rename to .doc/REAL_TIME_STREAMING.md diff --git a/STREAMING_TROUBLESHOOTING.md b/.doc/STREAMING_TROUBLESHOOTING.md similarity index 100% rename from STREAMING_TROUBLESHOOTING.md rename to .doc/STREAMING_TROUBLESHOOTING.md diff --git a/application_events.json b/application_events.json index d5df825..515df5a 100644 --- a/application_events.json +++ b/application_events.json @@ -9238,8 +9238,22 @@ "slot": 2, "error": "b' ISO : An error occurred during recv TCP : Connection timed out'" } + }, + { + "timestamp": "2025-08-11T15:17:54.090498", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-11T16:21:42.173167", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} } ], - "last_updated": "2025-08-11T13:53:59.849319", - "total_entries": 859 + "last_updated": "2025-08-11T16:21:42.173167", + "total_entries": 861 } \ No newline at end of file diff --git a/config/data/plc_config.json b/config/data/plc_config.json new file mode 100644 index 0000000..d18666f --- /dev/null +++ b/config/data/plc_config.json @@ -0,0 +1,21 @@ +{ + "plc_config": { + "ip": "10.1.33.12", + "rack": 0, + "slot": 2 + }, + "udp_config": { + "host": "127.0.0.1", + "port": 9870 + }, + "sampling_interval": 0.1, + "csv_config": { + "records_directory": "records", + "rotation_enabled": true, + "max_size_mb": 1000, + "max_days": 30, + "max_hours": null, + "cleanup_interval_hours": 24, + "last_cleanup": "2025-08-09T22:43:54.224975" + } +} \ No newline at end of file diff --git a/config/data/plc_datasets.json b/config/data/plc_datasets.json new file mode 100644 index 0000000..a39f71e --- /dev/null +++ b/config/data/plc_datasets.json @@ -0,0 +1,69 @@ +{ + "datasets": { + "DAR": { + "name": "DAR", + "prefix": "gateway_phoenix", + "variables": { + "UR29_Brix": { + "area": "db", + "offset": 1322, + "type": "real", + "streaming": true, + "db": 1011 + }, + "UR29_ma": { + "area": "db", + "offset": 1296, + "type": "real", + "streaming": true, + "db": 1011 + }, + "fUR29_Brix": { + "area": "db", + "offset": 1322, + "type": "real", + "streaming": false, + "db": 1011 + } + }, + "streaming_variables": [ + "UR29_Brix", + "UR29_ma" + ], + "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": [ + "Fast", + "DAR" + ], + "current_dataset_id": "Fast", + "version": "1.0", + "last_update": "2025-08-10T01:45:12.551768" +} \ No newline at end of file diff --git a/config/data/plot_sessions.json b/config/data/plot_sessions.json new file mode 100644 index 0000000..6077cf6 --- /dev/null +++ b/config/data/plot_sessions.json @@ -0,0 +1,23 @@ +{ + "plots": { + "plot_1": { + "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, + "session_id": "plot_1" + } + }, + "session_counter": 2, + "last_saved": "2025-08-10T00:37:46.525175", + "version": "1.0" + } \ No newline at end of file diff --git a/config/schema/datasets.schema.json b/config/schema/datasets.schema.json new file mode 100644 index 0000000..1629d96 --- /dev/null +++ b/config/schema/datasets.schema.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "datasets.schema.json", + "title": "Datasets Configuration", + "description": "Esquema para editar plc_datasets.json (múltiples datasets y variables)", + "type": "object", + "additionalProperties": false, + "properties": { + "datasets": { + "type": "object", + "title": "Datasets", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Dataset Name", + "description": "Nombre legible del dataset", + "minLength": 1, + "maxLength": 60 + }, + "prefix": { + "type": "string", + "title": "CSV Prefix", + "description": "Prefijo para archivos CSV", + "pattern": "^[a-zA-Z0-9_-]+$", + "minLength": 1, + "maxLength": 20 + }, + "variables": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "area": { + "type": "string", + "title": "Memory Area", + "enum": [ + "db", + "mw", + "m", + "pew", + "pe", + "paw", + "pa", + "e", + "a", + "mb" + ] + }, + "db": { + "type": [ + "integer", + "null" + ], + "title": "DB Number", + "minimum": 1, + "maximum": 9999 + }, + "offset": { + "type": "integer", + "title": "Offset", + "minimum": 0, + "maximum": 8191 + }, + "bit": { + "type": [ + "integer", + "null" + ], + "title": "Bit Position", + "minimum": 0, + "maximum": 7 + }, + "type": { + "type": "string", + "title": "Data Type", + "enum": [ + "real", + "int", + "bool", + "dint", + "word", + "byte", + "uint", + "udint", + "sint", + "usint" + ] + }, + "streaming": { + "type": "boolean", + "title": "Stream to PlotJuggler", + "default": false + } + }, + "required": [ + "area", + "offset", + "type" + ] + } + }, + "streaming_variables": { + "type": "array", + "title": "Streaming Variables", + "items": { + "type": "string" + }, + "default": [] + }, + "sampling_interval": { + "type": [ + "number", + "null" + ], + "title": "Sampling Interval (s)", + "description": "Vacío para usar el intervalo global", + "minimum": 0.01, + "maximum": 10 + }, + "enabled": { + "type": "boolean", + "title": "Dataset Enabled", + "default": false, + "enum": [ + true, + false + ], + "options": { + "enum_titles": [ + "Activate", + "Deactivate" + ] + } + }, + "created": { + "type": [ + "string", + "null" + ], + "title": "Created" + } + }, + "required": [ + "name", + "prefix", + "variables", + "streaming_variables" + ] + } + }, + "active_datasets": { + "type": "array", + "title": "Active Datasets", + "items": { + "type": "string" + }, + "default": [] + }, + "current_dataset_id": { + "type": [ + "string", + "null" + ], + "title": "Current Dataset Id" + }, + "version": { + "type": "string", + "title": "Version" + }, + "last_update": { + "type": [ + "string", + "null" + ], + "title": "Last Update" + } + }, + "required": [ + "datasets" + ] +} \ No newline at end of file diff --git a/config/schema/plc.schema.json b/config/schema/plc.schema.json new file mode 100644 index 0000000..ab4b575 --- /dev/null +++ b/config/schema/plc.schema.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "plc.schema.json", + "title": "PLC & UDP Configuration", + "description": "Esquema para editar plc_config.json", + "type": "object", + "additionalProperties": false, + "properties": { + "plc_config": { + "type": "object", + "title": "PLC Configuration", + "additionalProperties": false, + "properties": { + "ip": { + "type": "string", + "title": "PLC IP", + "description": "Dirección IP del PLC (S7-31x)", + "format": "ipv4", + "pattern": "^.+$" + }, + "rack": { + "type": "integer", + "title": "Rack", + "description": "Número de rack (0-7)", + "minimum": 0, + "maximum": 7, + "default": 0 + }, + "slot": { + "type": "integer", + "title": "Slot", + "description": "Número de slot (generalmente 2)", + "minimum": 0, + "maximum": 31, + "default": 2 + } + }, + "required": [ + "ip", + "rack", + "slot" + ] + }, + "udp_config": { + "type": "object", + "title": "UDP Configuration", + "additionalProperties": false, + "properties": { + "host": { + "type": "string", + "title": "UDP Host", + "pattern": "^.+$", + "default": "127.0.0.1" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 9870 + } + }, + "required": [ + "host", + "port" + ] + }, + "sampling_interval": { + "type": "number", + "minimum": 0.01, + "maximum": 10, + "title": "Sampling Interval (s)", + "description": "Intervalo global de muestreo en segundos", + "default": 0.1 + }, + "csv_config": { + "type": "object", + "title": "CSV Recording", + "additionalProperties": false, + "properties": { + "records_directory": { + "type": "string", + "title": "Records Directory", + "default": "records" + }, + "rotation_enabled": { + "type": "boolean", + "title": "Rotation", + "default": true, + "enum": [ + true, + false + ], + "options": { + "enum_titles": [ + "Activate", + "Deactivate" + ] + } + }, + "max_size_mb": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "title": "Max Size (MB)", + "default": 1000 + }, + "max_days": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "title": "Max Days", + "default": 30 + }, + "max_hours": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "title": "Max Hours", + "default": null + }, + "cleanup_interval_hours": { + "type": "integer", + "minimum": 1, + "title": "Cleanup Interval (h)", + "default": 24 + }, + "last_cleanup": { + "type": [ + "string", + "null" + ], + "title": "Last Cleanup" + } + }, + "required": [ + "records_directory", + "rotation_enabled", + "cleanup_interval_hours" + ] + } + }, + "required": [ + "plc_config", + "udp_config", + "sampling_interval", + "csv_config" + ] +} \ No newline at end of file diff --git a/config/schema/plots.schema.json b/config/schema/plots.schema.json new file mode 100644 index 0000000..35a8c57 --- /dev/null +++ b/config/schema/plots.schema.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "plots.schema.json", + "title": "Plot Sessions", + "description": "Esquema para editar plot_sessions.json (sesiones de gráfica)", + "type": "object", + "additionalProperties": false, + "properties": { + "plots": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Plot Name", + "description": "Nombre de la sesión de gráfica" + }, + "variables": { + "type": "array", + "title": "Variables", + "description": "Variables a graficar", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "time_window": { + "type": "integer", + "title": "Time Window (s)", + "description": "Ventana temporal en segundos", + "minimum": 5, + "maximum": 3600, + "default": 60 + }, + "y_min": { + "type": [ + "number", + "null" + ], + "title": "Y Min", + "description": "Vacío para auto" + }, + "y_max": { + "type": [ + "number", + "null" + ], + "title": "Y Max", + "description": "Vacío para auto" + }, + "trigger_variable": { + "type": [ + "string", + "null" + ], + "title": "Trigger Variable" + }, + "trigger_enabled": { + "type": "boolean", + "title": "Enable Trigger", + "default": false + }, + "trigger_on_true": { + "type": "boolean", + "title": "Trigger on True", + "default": true + }, + "session_id": { + "type": "string", + "title": "Session Id" + } + }, + "required": [ + "name", + "variables", + "time_window" + ] + } + }, + "session_counter": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "last_saved": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string", + "default": "1.0" + } + }, + "required": [ + "plots" + ] +} \ No newline at end of file diff --git a/config/schema/ui/datasets.uischema.json b/config/schema/ui/datasets.uischema.json new file mode 100644 index 0000000..b4fd928 --- /dev/null +++ b/config/schema/ui/datasets.uischema.json @@ -0,0 +1,42 @@ +{ + "datasets": { + "ui:description": "Define datasets, their variables and streaming flags.", + "items": { + "name": { + "ui:placeholder": "Temperature Sensors" + }, + "prefix": { + "ui:placeholder": "temp" + }, + "sampling_interval": { + "ui:widget": "UpDownWidget" + }, + "enabled": { + "ui:widget": "CheckboxWidget" + }, + "variables": { + "ui:description": "Variables inside this dataset", + "items": { + "area": { + "ui:widget": "SelectWidget" + }, + "db": { + "ui:widget": "UpDownWidget" + }, + "offset": { + "ui:widget": "UpDownWidget" + }, + "bit": { + "ui:widget": "UpDownWidget" + }, + "type": { + "ui:widget": "SelectWidget" + }, + "streaming": { + "ui:widget": "CheckboxWidget" + } + } + } + } + } +} \ No newline at end of file diff --git a/config/schema/ui/plc.uischema.json b/config/schema/ui/plc.uischema.json new file mode 100644 index 0000000..5da2348 --- /dev/null +++ b/config/schema/ui/plc.uischema.json @@ -0,0 +1,44 @@ +{ + "plc_config": { + "ip": { + "ui:placeholder": "192.168.1.100" + }, + "rack": { + "ui:widget": "UpDownWidget" + }, + "slot": { + "ui:widget": "UpDownWidget" + } + }, + "udp_config": { + "host": { + "ui:placeholder": "127.0.0.1" + }, + "port": { + "ui:widget": "UpDownWidget" + } + }, + "sampling_interval": { + "ui:widget": "UpDownWidget" + }, + "csv_config": { + "records_directory": { + "ui:placeholder": "records" + }, + "rotation_enabled": { + "ui:widget": "CheckboxWidget" + }, + "max_size_mb": { + "ui:widget": "UpDownWidget" + }, + "max_days": { + "ui:widget": "UpDownWidget" + }, + "max_hours": { + "ui:widget": "UpDownWidget" + }, + "cleanup_interval_hours": { + "ui:widget": "UpDownWidget" + } + } +} \ No newline at end of file diff --git a/config/schema/ui/plots.uischema.json b/config/schema/ui/plots.uischema.json new file mode 100644 index 0000000..5b9765c --- /dev/null +++ b/config/schema/ui/plots.uischema.json @@ -0,0 +1,21 @@ +{ + "plots": { + "items": { + "time_window": { + "ui:widget": "UpDownWidget" + }, + "y_min": { + "ui:widget": "UpDownWidget" + }, + "y_max": { + "ui:widget": "UpDownWidget" + }, + "trigger_enabled": { + "ui:widget": "CheckboxWidget" + }, + "trigger_on_true": { + "ui:widget": "CheckboxWidget" + } + } + } +} \ No newline at end of file diff --git a/core/config_manager.py b/core/config_manager.py index fa135c9..6f3d079 100644 --- a/core/config_manager.py +++ b/core/config_manager.py @@ -24,9 +24,11 @@ class ConfigManager: """Initialize configuration manager""" self.logger = logger - # Configuration file paths - self.config_file = resource_path("plc_config.json") - self.datasets_file = resource_path("plc_datasets.json") + # Configuration file paths (reorganized under config/data) + data_dir = os.path.join("config", "data") + os.makedirs(resource_path(data_dir), exist_ok=True) + self.config_file = resource_path(os.path.join(data_dir, "plc_config.json")) + self.datasets_file = resource_path(os.path.join(data_dir, "plc_datasets.json")) self.state_file = resource_path("system_state.json") # Default configurations diff --git a/core/plot_manager.py b/core/plot_manager.py index deb1c39..0580487 100644 --- a/core/plot_manager.py +++ b/core/plot_manager.py @@ -227,8 +227,10 @@ class PlotManager: self.event_logger = event_logger self.logger = logger - # Persistent storage - self.plots_file = resource_path("plot_sessions.json") + # Persistent storage (reorganized under config/data) + self.plots_file = resource_path( + os.path.join("config", "data", "plot_sessions.json") + ) # Load existing plots from disk self.load_plots() diff --git a/core/schema_manager.py b/core/schema_manager.py index 6806a34..cf0f99f 100644 --- a/core/schema_manager.py +++ b/core/schema_manager.py @@ -36,14 +36,18 @@ class ConfigSchemaManager: self.plot_manager = plot_manager self.logger = logger - self.schemas_dir = resource_path("schemas") + # Reorganized schema directories + self.schemas_dir = resource_path(os.path.join("config", "schema")) + self.ui_schemas_dir = resource_path(os.path.join("config", "schema", "ui")) self.schemas_index: Dict[str, Dict[str, Any]] = {} # Mapa de id -> ruta de archivo real de configuración + data_dir = resource_path(os.path.join("config", "data")) + os.makedirs(data_dir, exist_ok=True) self.config_files: Dict[str, str] = { - "plc": resource_path("plc_config.json"), - "datasets": resource_path("plc_datasets.json"), - "plots": resource_path("plot_sessions.json"), + "plc": os.path.join(data_dir, "plc_config.json"), + "datasets": os.path.join(data_dir, "plc_datasets.json"), + "plots": os.path.join(data_dir, "plot_sessions.json"), } self._load_all_schemas() @@ -83,6 +87,18 @@ class ConfigSchemaManager: raise ValueError(f"Schema '{schema_id}' not found") return self.schemas_index[schema_id] + def get_ui_schema(self, schema_id: str) -> Optional[Dict[str, Any]]: + """Load optional RJSF UI schema if present.""" + try: + path = os.path.join(self.ui_schemas_dir, f"{schema_id}.uischema.json") + if os.path.exists(path): + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + if self.logger: + self.logger.warning(f"Error loading UI schema for '{schema_id}': {e}") + return None + def read_config(self, config_id: str) -> Dict[str, Any]: if config_id == "plc": # Construir desde ConfigManager para asegurar consistencia diff --git a/frontend/index.html b/frontend/index.html index a686afe..59d7206 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,14 +1,17 @@ - - - - PLC S7-31x Streamer & Logger - React - - - -
- - - + + + + PLC S7-31x Streamer & Logger - React + + + + + +
+ + + + \ No newline at end of file diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..3f5ce65 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/record.png b/frontend/public/record.png new file mode 100644 index 0000000..3f5ce65 Binary files /dev/null and b/frontend/public/record.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e06e212..705462a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,5 @@ import React from 'react' +import recLogo from './assets/logo/record.png' import { Routes, Route, Link } from 'react-router-dom' import StatusPage from './pages/Status.jsx' import EventsPage from './pages/Events.jsx' @@ -12,7 +13,7 @@ function Home() {

- SIDEL + REC PLC S7-31x Streamer & Logger (React)

React base ready. We will migrate views incrementally.

@@ -39,7 +40,7 @@ function NavBar() {