diff --git a/.gitignore b/.gitignore index 68350d1..978aff0 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ MANIFEST # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec # Installer logs pip-log.txt diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..a383fb1 --- /dev/null +++ b/main.spec @@ -0,0 +1,189 @@ +# -*- mode: python ; coding: utf-8 -*- +import os +import sys + +block_cipher = None + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[ + # Include snap7.dll - now confirmed to be in project root + ('snap7.dll', '.'), + ], + datas=[ + # Include the entire frontend build + ('frontend/dist', 'frontend/dist'), + + # Include configuration directories and schemas + ('config', 'config'), + + # Include core modules + ('core', 'core'), + + # Include utils + ('utils', 'utils'), + + # Include translation files + ('translation.json', '.'), + ('i18n.js', '.'), + + # Create records directory structure + ('records', 'records'), + ], + hiddenimports=[ + # Flask and web dependencies + 'jinja2.ext', + 'flask', + 'flask_cors', + 'flask_socketio', + 'socketio', + 'werkzeug', + + # JSON Schema validation + 'jsonschema', + 'jsonschema.validators', + 'jsonschema._format', + 'jsonschema._types', + + # PLC and system dependencies + 'snap7', + 'psutil._pswindows', + 'psutil._psutil_windows', + + # Data processing + 'pandas', + 'numpy', + + # Threading and networking + 'threading', + 'socket', + 'json', + 'csv', + 'datetime', + 'pathlib', + + # Core modules (explicit imports) + 'core.config_manager', + 'core.plc_client', + 'core.plc_data_streamer', + 'core.event_logger', + 'core.instance_manager', + 'core.schema_manager', + 'core.streamer', + 'core.plot_manager', + 'core.historical_cache', + 'core.performance_monitor', + 'core.priority_manager', + 'core.rotating_logger', + + # Utils modules + 'utils.csv_validator', + 'utils.json_manager', + 'utils.symbol_loader', + 'utils.symbol_processor', + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[ + # Exclude unnecessary packages to reduce size + 'matplotlib', + 'scipy', + 'IPython', + 'notebook', + 'jupyter', + 'tests', + 'unittest', + 'pydoc', + 'doctest', + ], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='S7_Streamer_Logger', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, # True para ver los logs del servidor en una consola. + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='main') + +# Post-build: Copy config directory to the same level as the executable +import shutil +import os + +def copy_config_external(): + """Copy config directory to external location for runtime access""" + try: + # Get absolute paths + current_dir = os.path.abspath('.') + source_config = os.path.join(current_dir, 'config') + dist_main_dir = os.path.join(current_dir, 'dist', 'main') + dest_config = os.path.join(dist_main_dir, 'config') + + print(f"Current directory: {current_dir}") + print(f"Source config: {source_config}") + print(f"Destination config: {dest_config}") + + # Ensure dist/main directory exists + os.makedirs(dist_main_dir, exist_ok=True) + + # Remove existing config if present + if os.path.exists(dest_config): + shutil.rmtree(dest_config) + print(f"Removed existing config at: {dest_config}") + + # Copy config directory to dist/main/config + if os.path.exists(source_config): + shutil.copytree(source_config, dest_config) + print(f"✓ Config directory copied to: {dest_config}") + return True + else: + print(f"✗ Source config directory not found: {source_config}") + return False + + except Exception as e: + print(f"✗ Error copying config directory: {e}") + return False + +# Execute the copy operation +copy_config_external() + +def config_path(relative_path): + """Get path to config file, checking external location first when running as executable""" + if getattr(sys, 'frozen', False): + # Running as executable - config should be at same level as executable + executable_dir = os.path.dirname(sys.executable) + external_config = os.path.join(executable_dir, 'config', relative_path) + + if os.path.exists(external_config): + return external_config + + # Fallback to internal config within _internal + internal_config = os.path.join(executable_dir, '_internal', 'config', relative_path) + if os.path.exists(internal_config): + return internal_config + + raise FileNotFoundError(f"Configuration file not found: {relative_path}") + else: + # Running as script - use standard path + base_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(base_dir) + return os.path.join(project_root, 'config', relative_path) diff --git a/requirements.txt b/requirements.txt index 410f187..8a32d78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,24 @@ +# Core Web Framework Flask==2.3.3 -python-snap7==1.3 -psutil==5.9.5 -flask-socketio==5.3.6 -jsonschema==4.22.0 Flask-Cors==4.0.0 -pandas \ No newline at end of file + +# PLC Communication +python-snap7==1.3 + +# System Monitoring & Process Management +psutil==5.9.5 + +# Data Processing & Analysis +pandas==2.3.1 +numpy==2.2.6 + +# JSON Schema Validation +jsonschema==4.22.0 + +# Note: The following dependencies are automatically installed with Flask: +# - Werkzeug==3.1.3 (WSGI toolkit) +# - Jinja2==3.1.6 (templating engine) +# - click (CLI utilities) +# - itsdangerous (security utilities) +# - blinker (signals) +# - MarkupSafe (safe string handling) \ No newline at end of file diff --git a/snap7.dll b/snap7.dll new file mode 100644 index 0000000..e1baafc Binary files /dev/null and b/snap7.dll differ