diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec273bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo + +# Runtime data (device-specific, contains IPs and sensitive config) +data/config.txt +data/device_info.txt +data/log.txt +data/tag.txt +data/idmasa.txt + +# Offline wheel cache (large, OS-specific) +Files/reposytory/ +Files/system_packages/ + +# Misc +.lgd-nfy0 +.DS_Store +Thumbs.db diff --git a/README_DEPENDENCIES.md b/README_DEPENDENCIES.md deleted file mode 100644 index d5b12f2..0000000 --- a/README_DEPENDENCIES.md +++ /dev/null @@ -1,186 +0,0 @@ -# Prezenta App - Robust Dependency Management System - -## Overview -The app.py has been updated to include a comprehensive, self-contained dependency management system that automatically checks and installs required packages from a local repository. This makes the system completely offline-capable and resilient to network issues. - -## Key Features - -### 🔧 **Automatic Dependency Installation** -- **Self-contained**: No need for separate launcher scripts or shell scripts -- **Offline capability**: Installs packages from local `./Files/reposytory` directory -- **Smart detection**: Checks if packages are already installed before attempting installation -- **Fallback mechanisms**: Multiple installation methods (pip, apt, local wheels) - -### đŸ›Ąī¸ **Robust Error Handling** -- **Network resilience**: Handles socket errors gracefully with file-based fallbacks -- **Import safety**: Safe import functions that don't crash the app -- **Graceful degradation**: App continues to work even if some optional features fail -- **Comprehensive logging**: All operations are logged for debugging - -### đŸ“Ļ **Supported Packages** -The system automatically manages these packages: -- `rdm6300` - RFID reader library (critical) -- `requests` - HTTP library (critical) -- `aiohttp` - Async HTTP library (optional) -- `flask` - Web server for command interface (optional) -- `gpiozero` - GPIO control (falls back to dummy if not available) -- All required dependencies (urllib3, certifi, charset_normalizer, etc.) - -## How It Works - -### 1. **Startup Dependency Check** -When the app starts, it: -1. Checks each required package using `importlib.util.find_spec()` -2. Identifies missing packages -3. Attempts installation from local wheel files in `./Files/reposytory` -4. Falls back to pip/apt if needed -5. Continues execution even if some packages fail to install - -### 2. **Safe Import System** -```python -def safe_import(module_name, package_name=None): - """Safely import a module with error handling""" -``` -- Imports modules without crashing the app -- Returns None for missing modules -- Allows the app to adapt to available packages - -### 3. **Network Error Resilience** -- Device hostname/IP saved to `./data/device_info.txt` -- Automatically loads from file when socket errors occur -- Never crashes due to network resolution issues - -### 4. **Graceful Feature Degradation** -- **No Flask**: Command server is disabled with warning -- **No gpiozero**: LED functions become print statements -- **No aiohttp**: Falls back to synchronous requests -- **No network**: Uses cached device information - -## Repository Structure - -### Required Files in `./Files/reposytory/`: -``` -├── rdm6300-0.1.1-py3-none-any.whl -├── requests-2.32.3-py3-none-any.whl -├── aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -├── flask-*.whl (optional) -├── urllib3-2.3.0-py3-none-any.whl -├── certifi-2025.1.31-py3-none-any.whl -├── charset_normalizer-3.4.1-py3-none-any.whl -├── idna-3.10-py3-none-any.whl -├── multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -├── aiosignal-1.3.2-py2.py3-none-any.whl -├── frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -├── attrs-25.3.0-py3-none-any.whl -├── yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -├── aiohappyeyeballs-2.6.1-py3-none-any.whl -└── propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -``` - -## Installation Process - -### Automatic (Recommended) -Simply run the app - dependencies will be installed automatically: -```bash -cd /home/pi/Desktop/prezenta -python3 app.py -``` - -### Manual Testing -Test the dependency system: -```bash -cd /home/pi/Desktop/prezenta -python3 test_dependencies.py -``` - -## Folder Structure - -The prezenta folder has been streamlined for maximum simplicity: - -``` -prezenta/ -├── app.py # Main application (self-contained) -├── config.py # Configuration module -├── test_dependencies.py # Testing and validation script -├── README_DEPENDENCIES.md # This documentation -├── data/ # Application data -│ ├── device_info.txt # Network configuration cache (auto-created) -│ ├── idmasa.txt # Device identifier -│ └── tag.txt # Backup data -└── Files/ # Local package repository - └── reposytory/ # Wheel files for offline installation - ├── rdm6300-*.whl - ├── requests-*.whl - ├── aiohttp-*.whl - └── ... (all dependencies) -``` - -### Removed Files (No Longer Needed) -- ~~`launcher.py`~~ - App is now self-launching -- ~~`libraries.sh`~~ - Dependency installation integrated into app.py -- ~~`check_dependencies.py`~~ - Functionality moved to app.py - -## Features Added - -### 1. **Self-Installing Dependencies** -- Checks all required packages on startup -- Installs missing packages from local repository -- No external network dependencies for package installation - -### 2. **Network Error Recovery** -- Handles `socket.gaierror` gracefully -- Saves working network configuration to file -- Loads from file when network issues occur - -### 3. **Modular Feature Set** -- Core RFID functionality always works -- Optional features (web interface, LEDs) degrade gracefully -- Clear status messages for all operations - -### 4. **Enhanced Logging** -- Dependency check results logged -- Installation attempts logged -- Network fallback operations logged - -## Troubleshooting - -### Common Issues - -1. **"Repository not found"** - - Ensure `./Files/reposytory` directory exists - - Check that wheel files are present - -2. **"Permission denied during installation"** - - Script uses `--break-system-packages` flag - - May need to run with appropriate permissions - -3. **"rdm6300 is required"** - - This is critical - app will exit if rdm6300 can't be installed - - Check that `rdm6300-0.1.1-py3-none-any.whl` exists in repository - -4. **"Flask not available - Command server disabled"** - - Non-critical - RFID functionality continues to work - - Install Flask manually if remote command capability is needed - -### Recovery Steps - -1. **Check repository**: Ensure all wheel files are present -2. **Test installation**: Run `python3 test_dependencies.py` -3. **Manual install**: Install missing packages manually with pip -4. **Check logs**: Review console output for specific error messages - -## Benefits - -1. **Zero-configuration deployment**: Just copy files and run -2. **Offline operation**: No internet required after initial setup -3. **Fault tolerance**: Continues working even with partial failures -4. **Easy maintenance**: All dependencies managed automatically -5. **Clear diagnostics**: Detailed status reporting for troubleshooting - -## Version History - -- **v2.4**: Added robust dependency management and network error handling -- **v2.3**: Added remote command execution capabilities -- **v2.2**: Basic RFID and logging functionality - -The system is now production-ready for deployment in environments with limited or no internet connectivity, providing maximum reliability and ease of deployment. diff --git a/REPOSITORY_UPDATE_SUMMARY.md b/REPOSITORY_UPDATE_SUMMARY.md deleted file mode 100644 index 7781b14..0000000 --- a/REPOSITORY_UPDATE_SUMMARY.md +++ /dev/null @@ -1,88 +0,0 @@ -# Repository Updates Summary - -## Date: August 14, 2025 - -### Changes Made - -#### 1. App.py Version 2.7 - Path Detection Fix -**Problem Solved**: Auto-update functionality was failing on devices with case-sensitive file systems where the folder was named "Prezenta" (uppercase P) instead of "prezenta" (lowercase p). - -**Changes Made**: -- Replaced hardcoded paths with dynamic path detection using `__file__` -- Auto-update now works regardless of folder case sensitivity -- Enhanced path resolution for Files/repository and system_packages directories - -**Code Changes**: -```python -# OLD (hardcoded): -LOCAL_APP_PATH = "/home/pi/Desktop/prezenta/app.py" -LOCAL_REPO_PATH = "/home/pi/Desktop/prezenta/Files/reposytory" - -# NEW (dynamic): -current_script_path = os.path.abspath(__file__) -local_base_dir = os.path.dirname(current_script_path) -LOCAL_APP_PATH = current_script_path -LOCAL_REPO_PATH = os.path.join(local_base_dir, "Files", "reposytory") -``` - -#### 2. Server.py - Port 80 Communication Update -**Problem Solved**: Server was still trying to communicate with devices on port 5000 instead of port 80. - -**Changes Made**: -- Updated all device communication endpoints to use port 80 -- Fixed command execution, status checks, and auto-update requests -- Ensured consistent port usage across all server-device communications - -**Updated Functions**: -- `execute_command_on_device()` - now uses port 80 -- `get_device_status()` - now uses port 80 -- `auto_update_devices()` - now uses port 80 - -#### 3. Repository Structure Updates -**Added New Files**: -- `README_DEPENDENCIES.md` - Comprehensive dependency documentation -- `setup_port_capability.sh` - Port 80 capability setup script -- `Files/system_packages/` - System package repository for offline installation -- Enhanced wheel file repository with latest packages - -#### 4. Server Monitoring System (New Repository) -**Initialized**: `/home/pi/Desktop/Server_Monitorizare` as a git repository - -**Features**: -- Complete device management dashboard -- Remote command execution on port 80 -- Auto-update coordination -- Database reset functionality -- Server logs filtering and interface -- Comprehensive error handling - -### Git Status - -#### Prezenta Repository -- **Current Version**: 2.7 -- **Latest Commit**: `6975e18 - v2.7: Fixed auto-update path detection for case-sensitive file systems` -- **Status**: All changes committed successfully -- **Remote**: Not configured (local repository) - -#### Server_Monitorizare Repository -- **Status**: Newly initialized git repository -- **Latest Commit**: `42989aa - Initial server monitoring system with port 80 support` -- **Status**: All files committed successfully -- **Remote**: Not configured (local repository) - -### Testing Results -- ✅ Dynamic path detection working correctly -- ✅ Port 80 communication updated in server -- ✅ Auto-update functionality fixed for case-sensitive systems -- ✅ Server monitoring system fully operational - -### Next Steps -If you want to push to remote repositories: -1. Configure remote origins for both repositories -2. Push commits to remote branches -3. Set up CI/CD if needed - -### File Locations -- Main App: `/home/pi/Desktop/prezenta/app.py` (v2.7) -- Server: `/home/pi/Desktop/Server_Monitorizare/server.py` -- Documentation: This summary and README_DEPENDENCIES.md diff --git a/app.py b/app.py index 93cd139..ed30a65 100644 --- a/app.py +++ b/app.py @@ -129,8 +129,258 @@ except ImportError as e: def jsonify(data): return data +import configparser import json + +def load_config(): + """ + Load application configuration from data/config.txt (INI format). + Falls back to legacy file (device_info.txt) and hardcoded defaults. + """ + config_path = "./data/config.txt" + defaults = { + "chrome_url": "http://10.76.140.17/iweb_v2/index.php/traceability/production", + "chrome_local_url": "", + "chrome_insecure_origin": "http://10.76.140.17", + "card_post_base_url": "https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record", + "server_log_url": "http://rpi-ansible:80/logs", + "update_host": "rpi-ansible", + "update_user": "pi", + "internet_check_host": "10.76.140.17", + "device_name": "notconfig", + "device_hostname": "unknown-device", + "device_ip": "127.0.0.1", + "device_location": "", + "device_info_reviewed_at": "1970-01-01T00:00:00", + "last_synced": "1970-01-01T00:00:00", + } + + cfg = dict(defaults) + parser = configparser.ConfigParser() + + if os.path.exists(config_path): + try: + parser.read(config_path) + + if parser.has_section("chrome"): + if parser.has_option("chrome", "chrome_url"): + cfg["chrome_url"] = parser.get("chrome", "chrome_url") + if parser.has_option("chrome", "chrome_local_url"): + cfg["chrome_local_url"] = parser.get("chrome", "chrome_local_url") + if parser.has_option("chrome", "chrome_insecure_origin"): + cfg["chrome_insecure_origin"] = parser.get("chrome", "chrome_insecure_origin") + + if parser.has_section("card_api"): + if parser.has_option("card_api", "base_url"): + cfg["card_post_base_url"] = parser.get("card_api", "base_url") + + if parser.has_section("server"): + if parser.has_option("server", "log_url"): + cfg["server_log_url"] = parser.get("server", "log_url") + if parser.has_option("server", "update_host"): + cfg["update_host"] = parser.get("server", "update_host") + if parser.has_option("server", "update_user"): + cfg["update_user"] = parser.get("server", "update_user") + if parser.has_option("server", "internet_check_host"): + cfg["internet_check_host"] = parser.get("server", "internet_check_host") + + if parser.has_section("device"): + if parser.has_option("device", "work_place"): + cfg["device_name"] = parser.get("device", "work_place") + elif parser.has_option("device", "name"): + cfg["device_name"] = parser.get("device", "name") + if parser.has_option("device", "hostname"): + cfg["device_hostname"] = parser.get("device", "hostname") + if parser.has_option("device", "ip"): + cfg["device_ip"] = parser.get("device", "ip") + if parser.has_option("device", "info_reviewed_at"): + cfg["device_info_reviewed_at"] = parser.get("device", "info_reviewed_at") + if parser.has_option("device", "location"): + cfg["device_location"] = parser.get("device", "location") + + if parser.has_section("meta"): + if parser.has_option("meta", "last_synced"): + cfg["last_synced"] = parser.get("meta", "last_synced") + + print(f"\u2713 Configuration loaded from {config_path}") + except Exception as e: + print(f"Warning: Could not parse {config_path}: {e}. Using defaults.") + else: + # Fall back to legacy individual files + try: + with open("./data/device_info.txt", "r") as f: + lines = f.read().strip().split('\n') + if len(lines) >= 1 and lines[0].strip(): + cfg["device_hostname"] = lines[0].strip() + if len(lines) >= 2 and lines[1].strip(): + cfg["device_ip"] = lines[1].strip() + except Exception: + pass + print("config.txt not found - using legacy files and defaults") + + return cfg + + +# --------------------------------------------------------------------------- +# Server config sync (runs once at startup) +# --------------------------------------------------------------------------- + +def _get_mac_address(): + """Return the MAC address of the primary network interface.""" + for iface in ['eth0', 'wlan0', 'eth1']: + mac_path = f'/sys/class/net/{iface}/address' + try: + if os.path.exists(mac_path): + with open(mac_path) as f: + mac = f.read().strip() + if mac and mac != '00:00:00:00:00:00': + return mac + except Exception: + continue + # Fallback: derive from UUID node + import uuid as _uuid + raw = _uuid.getnode() + return ':'.join(f'{(raw >> i) & 0xff:02x}' for i in range(40, -1, -8)) + + +def _write_config_from_server(new_cfg): + """Overwrite data/config.txt with config received from the server.""" + import configparser as _cp + p = _cp.ConfigParser() + + p.add_section("chrome") + p.set("chrome", "chrome_url", new_cfg.get("chrome_url", "")) + p.set("chrome", "chrome_local_url", new_cfg.get("chrome_local_url", "")) + p.set("chrome", "chrome_insecure_origin", new_cfg.get("chrome_insecure_origin", "")) + + p.add_section("card_api") + p.set("card_api", "base_url", new_cfg.get("card_api_base_url", "")) + + p.add_section("server") + p.set("server", "log_url", new_cfg.get("server_log_url", "")) + p.set("server", "update_host", new_cfg.get("update_host", "")) + p.set("server", "update_user", new_cfg.get("update_user", "")) + p.set("server", "internet_check_host", new_cfg.get("internet_check_host", "")) + + p.add_section("device") + p.set("device", "work_place", new_cfg.get("device_name", "notconfig")) + p.set("device", "hostname", new_cfg.get("hostname", "")) + p.set("device", "ip", new_cfg.get("device_ip", "")) + p.set("device", "location", new_cfg.get("location") or new_cfg.get("device_location", "")) + p.set("device", "info_reviewed_at", new_cfg.get("info_reviewed_at") or "1970-01-01T00:00:00") + + p.add_section("meta") + sync_ts = new_cfg.get("config_updated_at") or datetime.now().isoformat() + p.set("meta", "last_synced", sync_ts) + + os.makedirs("./data", exist_ok=True) + with open("./data/config.txt", "w") as f: + f.write("# WMT Application Configuration\n") + f.write(f"# Synced from server: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") + p.write(f) + print(f"\u2713 config.txt written from server (server ts: {sync_ts})") + + +def sync_config_with_server(): + """ + Called once at startup. + - Derives base URL from server_log_url in APP_CONFIG. + - If server config is newer than last_synced → pull and overwrite config.txt, reload APP_CONFIG. + - Otherwise → send a device-info update request to the server. + Returns True if config was pulled from server. + """ + global APP_CONFIG + try: + import requests as _req + import urllib.parse as _up + + parsed = _up.urlparse(APP_CONFIG.get("server_log_url", "http://rpi-ansible:80/logs")) + server_base = f"{parsed.scheme}://{parsed.netloc}" + mac = _get_mac_address() + + last_synced_str = APP_CONFIG.get("last_synced", "1970-01-01T00:00:00") + try: + last_synced = datetime.fromisoformat(last_synced_str) + except Exception: + last_synced = datetime(1970, 1, 1) + + local_info_reviewed_str = APP_CONFIG.get("device_info_reviewed_at", "1970-01-01T00:00:00") + try: + local_info_reviewed = datetime.fromisoformat(local_info_reviewed_str) + except Exception: + local_info_reviewed = datetime(1970, 1, 1) + + print(f"Checking server config (MAC={mac}, last_synced={last_synced_str}, info_reviewed_at={local_info_reviewed_str}) ...") + + # --- Step 1: get server timestamp --- + ts_resp = _req.get( + f"{server_base}/api/wmt/config/timestamp", + params={"mac": mac}, timeout=5 + ) + ts_resp.raise_for_status() + ts_data = ts_resp.json() + + server_ts_str = ts_data.get("latest_updated_at", "1970-01-01T00:00:00") + try: + server_ts = datetime.fromisoformat(server_ts_str) + except Exception: + server_ts = datetime(1970, 1, 1) + + server_info_reviewed_str = ts_data.get("device_info_reviewed_at") or "1970-01-01T00:00:00" + try: + server_info_reviewed = datetime.fromisoformat(server_info_reviewed_str) + except Exception: + server_info_reviewed = datetime(1970, 1, 1) + + # Pull if global settings are newer OR if admin has reviewed device info more recently + needs_pull = server_ts > last_synced or server_info_reviewed > local_info_reviewed + + if needs_pull: + # --- Step 2a: pull config --- + print(f"Server config is newer ({server_ts_str}), pulling ...") + cfg_resp = _req.get(f"{server_base}/api/wmt/config/{mac}", timeout=5) + cfg_resp.raise_for_status() + _write_config_from_server(cfg_resp.json()) + APP_CONFIG = load_config() + print("\u2705 Config synced from server.") + return True + else: + # --- Step 2b: push device info --- + print("Local config is current, sending device info to server ...") + try: + _hostname = socket.gethostname() + _ip = socket.gethostbyname(_hostname) + except Exception: + _hostname = APP_CONFIG.get("device_hostname", "") + _ip = APP_CONFIG.get("device_ip", "") + + _req.post( + f"{server_base}/api/wmt/config/update_request", + json={ + "mac_address": mac, + "device_name": APP_CONFIG.get("device_name", ""), + "hostname": _hostname, + "device_ip": _ip, + "client_config_mtime": last_synced_str, + "client_info_reviewed_at": local_info_reviewed_str, + }, + timeout=5, + ) + print("\u2705 Device info update request sent.") + return False + + except Exception as e: + print(f"Config sync skipped (server unreachable or error): {e}") + return False + + +# Load global application configuration +APP_CONFIG = load_config() + +# Attempt to sync with server at startup (non-blocking – failures are logged and ignored) +sync_config_with_server() + # Early configuration mode detection (before heavy initialization) def early_launch_configuration_mode(): """ @@ -139,21 +389,23 @@ def early_launch_configuration_mode(): try: print("🔧 Configuration mode detected - launching Screen.html in Chromium") - # Get absolute path to Screen.html - current_dir = os.path.dirname(os.path.abspath(__file__)) - screen_html_path = os.path.join(current_dir, "Files", "Screen.html") - + # Path to Screen.html relative to the WMT working directory + screen_html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Files", "Screen.html") + screen_html_relative = "./Files/Screen.html" + if os.path.exists(screen_html_relative): + screen_html_path = os.path.abspath(screen_html_relative) + if not os.path.exists(screen_html_path): print(f"❌ Screen.html not found at: {screen_html_path}") return False print(f"📄 Loading Screen.html from: {screen_html_path}") - # Terminate any existing Chromium processes chromium_process_name = "chromium" - # Local log only in configuration mode - logging.info("Refreshing Chromium process (configuration mode)") + # Local log only in configuration mode + logging.info("Refreshing Chromium process (configuration mode)") + try: subprocess.run(["pkill", "-f", chromium_process_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(5) # Wait for processes to terminate @@ -168,11 +420,12 @@ def early_launch_configuration_mode(): except Exception as e: logging.error(f"Failed to refresh Chromium process (configuration mode): {e}") return False + print("✅ Configuration mode launched successfully") print("â„šī¸ Network connectivity checks disabled") print("â„šī¸ Server status messages disabled") print("🔧 Device running in configuration mode") - print("🔧 To exit configuration mode, change idmasa.txt content to device name") + print("🔧 To exit configuration mode, change work_place in config.txt") return True except FileNotFoundError: @@ -189,24 +442,23 @@ print("CONFIGURATION MODE DETECTION") print("=" * 60) try: - with open("./data/idmasa.txt", "r") as f: - name = f.readline().strip() or "noconfig" + name = APP_CONFIG.get("device_name", "noconfig") print(f"✓ Device name loaded: {name}") - + # Check if device is in configuration mode if name.lower() == "notconfig": print("🔧 Device configured for setup mode (notconfig)") - + # Launch configuration mode if early_launch_configuration_mode(): print("🚀 Configuration mode active - application will run in setup mode") print("âš ī¸ Network connectivity checks are DISABLED") print("âš ī¸ Server status messages are DISABLED") - print("🔧 Change idmasa.txt to device name to exit configuration mode") - + print("🔧 Change config.txt [device] name to exit configuration mode") + # Set global flag for configuration mode CONFIGURATION_MODE = True - + print("🔧 Configuration mode activated - continuing with RFID reader initialization") print("🔧 Network and server monitoring will remain disabled") else: @@ -215,21 +467,9 @@ try: else: print("✅ Device in normal operation mode") CONFIGURATION_MODE = False - -except FileNotFoundError: - print("Warning: idmasa.txt not found, using default 'noconfig'") - name = "noconfig" - CONFIGURATION_MODE = False - # Create the file with default value - try: - os.makedirs("./data", exist_ok=True) - with open("./data/idmasa.txt", "w") as f: - f.write("noconfig") - print("✓ Created default idmasa.txt file") - except Exception as e: - print(f"Could not create idmasa.txt: {e}") + except Exception as e: - print(f"Error reading idmasa.txt: {e}") + print(f"Error in configuration mode detection: {e}") name = "noconfig" CONFIGURATION_MODE = False @@ -305,7 +545,6 @@ def check_system_requirements(): # 2. Check required files and create defaults if missing required_files = { - './data/idmasa.txt': 'noconfig', './data/log.txt': '', './data/tag.txt': '', './data/device_info.txt': 'unknown-device\n127.0.0.1\n' @@ -609,11 +848,23 @@ def get_device_info(): except Exception as file_error: print(f"Could not load from file: {file_error}") - # Final fallback if everything fails + # Final fallback: use values from APP_CONFIG (config.txt [device] section) + try: + cfg_hostname = APP_CONFIG.get("device_hostname", "") + cfg_ip = APP_CONFIG.get("device_ip", "") + if cfg_hostname and cfg_hostname != "unknown-device": + hostname = cfg_hostname + device_ip = cfg_ip or "127.0.0.1" + print(f"Loaded from config.txt - Hostname: {hostname}, IP: {device_ip}") + return hostname, device_ip + except Exception: + pass + + # Absolute last resort defaults print("All methods failed - Using default values") hostname = hostname or "unknown-device" device_ip = "127.0.0.1" - + # Try to save these default values for next time try: os.makedirs("./data", exist_ok=True) @@ -622,7 +873,7 @@ def get_device_info(): print(f"Saved fallback values to {config_file}") except Exception as e: print(f"Could not save fallback values: {e}") - + return hostname, device_ip # Perform system initialization (first run setup) @@ -652,29 +903,42 @@ def delete_old_logs(): else: log_info_with_server(f"Log file does not exist: {log_file}") -# Function to read the name (idmasa) from the file +# Function to read the work place name from config def read_name_from_file(): try: - with open("./data/idmasa.txt", "r") as file: - n_masa = file.readline().strip() + n_masa = APP_CONFIG.get("device_name", "") + if n_masa: return n_masa - except FileNotFoundError: - logging.error("File ./data/idmasa.txt not found.") - return "unknown" + except Exception: + pass + return "unknown" + +def _get_os_version() -> str: + """Read OS pretty-name from /etc/os-release (Raspberry Pi OS, etc.).""" + try: + with open("/etc/os-release") as f: + for line in f: + if line.startswith("PRETTY_NAME="): + return line.split("=", 1)[1].strip().strip('"') + except Exception: + pass + return "" # Function to send logs to a remote server for the Server_monitorizare APP def send_log_to_server(log_message, n_masa, hostname, device_ip): - host = hostname - device = device_ip try: - log_data = { - "hostname": str(host), - "device_ip": str(device), + "hostname": str(hostname), + "device_ip": str(device_ip), "nume_masa": str(n_masa), - "log_message": str(log_message) + "log_message": str(log_message), + # Device metadata – keeps the server record up to date automatically + "device_type": "Raspberry Pi", + "os_version": _get_os_version(), + "location": APP_CONFIG.get("device_location", ""), + "mac_address": _get_mac_address(), } - server_url = "http://rpi-ansible:80/logs" # Replace with your server's URL + server_url = APP_CONFIG.get("server_log_url", "http://rpi-ansible:80/logs") print(log_data) # Debugging: Print log_data to verify its contents response = requests.post(server_url, json=log_data, timeout=5) response.raise_for_status() @@ -683,7 +947,7 @@ def send_log_to_server(log_message, n_masa, hostname, device_ip): logging.error("Failed to send log to server: %s", e) # Wrapper for logging.info to also send logs to the server Monitorizare APP def log_info_with_server(message): - n_masa = read_name_from_file() # Read name (idmasa) from the file + n_masa = read_name_from_file() # Read work place name from config formatted_message = f"{message} (n_masa: {n_masa})" # Format the message logging.info(formatted_message) # Log the formatted message @@ -706,6 +970,7 @@ def execute_system_command(command): allowed_commands = [ "sudo apt update", "sudo apt upgrade -y", + "sudo apt update && sudo apt upgrade -y", # Combined update and upgrade "sudo apt autoremove -y", "sudo apt autoclean", "sudo reboot", @@ -811,9 +1076,9 @@ if FLASK_AVAILABLE: Checks version, downloads newer files if available, and restarts the device """ try: - # Configuration - SERVER_HOST = "rpi-ansible" - SERVER_USER = "pi" + # Configuration (read from APP_CONFIG, falling back to hardcoded defaults) + SERVER_HOST = APP_CONFIG.get("update_host", "rpi-ansible") + SERVER_USER = APP_CONFIG.get("update_user", "pi") SERVER_PASSWORD = "Initial01!" SERVER_APP_PATH = "/home/pi/Desktop/prezenta/app.py" SERVER_REPO_PATH = "/home/pi/Desktop/prezenta/Files/reposytory" @@ -990,6 +1255,76 @@ sudo reboot except: pass + @command_app.route('/update_config', methods=['POST']) + def update_config_endpoint(): + """ + Update configuration from Server_Monitorizare_v2. + Accepts a JSON body with sections matching config.txt structure. + Example body: {"chrome": {"chrome_url": "http://..."}} + """ + global APP_CONFIG + + ALLOWED_SECTIONS = { + "chrome": ["chrome_url", "chrome_local_url", "chrome_insecure_origin"], + "card_api": ["base_url"], + "server": ["log_url", "update_host", "update_user", "internet_check_host"], + "device": ["name", "hostname", "ip"], + } + + try: + data = request.json + if not data: + return jsonify({"error": "JSON body required"}), 400 + + config_path = "./data/config.txt" + parser = configparser.ConfigParser() + if os.path.exists(config_path): + parser.read(config_path) + + updated_keys = [] + for section, allowed_keys in ALLOWED_SECTIONS.items(): + if section in data and isinstance(data[section], dict): + if not parser.has_section(section): + parser.add_section(section) + for key in allowed_keys: + if key in data[section]: + parser.set(section, key, str(data[section][key])) + updated_keys.append(f"{section}.{key}") + + os.makedirs("./data", exist_ok=True) + with open(config_path, "w") as f: + f.write("# WMT Application Configuration\n") + f.write(f"# Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") + parser.write(f) + + APP_CONFIG = load_config() + log_info_with_server(f"Configuration updated via API: {updated_keys}") + return jsonify({"status": "success", "updated_keys": updated_keys}), 200 + + except Exception as e: + log_info_with_server(f"Error updating config: {str(e)}") + return jsonify({"error": f"Failed to update configuration: {str(e)}"}), 500 + + @command_app.route('/reload_config', methods=['POST']) + def reload_config_endpoint(): + """ + Reload configuration from data/config.txt into memory without restarting. + """ + global APP_CONFIG + try: + APP_CONFIG = load_config() + log_info_with_server("Configuration reloaded via API") + return jsonify({ + "status": "success", + "message": "Configuration reloaded", + "chrome_url": APP_CONFIG.get("chrome_url"), + "card_post_base_url": APP_CONFIG.get("card_post_base_url"), + "server_log_url": APP_CONFIG.get("server_log_url"), + "device_name": APP_CONFIG.get("device_name"), + }), 200 + except Exception as e: + return jsonify({"error": f"Failed to reload config: {str(e)}"}), 500 + def start_command_server(): """ Start the Flask server with enhanced port handling and fallback @@ -1100,7 +1435,7 @@ def post_backup_data(): # Function to check internet connection def check_internet_connection(): - hostname = "10.76.140.17" + hostname = APP_CONFIG.get("internet_check_host", "10.76.140.17") cmd_block_wifi = 'sudo rfkill block wifi' cmd_unblock_wifi = 'sudo rfkill unblock wifi' log_info_with_server('Internet connection check loaded') @@ -1133,10 +1468,11 @@ def check_internet_connection(): time.sleep(5) # Wait for processes to terminate # Relaunch Chromium - url = "10.76.140.17/iweb_v2/index.php/traceability/production" + url = APP_CONFIG.get("chrome_url", "http://10.76.140.17/iweb_v2/index.php/traceability/production") + chrome_insecure = APP_CONFIG.get("chrome_insecure_origin", "http://10.76.140.17") subprocess.Popen( - ["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen", - "--unsafely-treat-insecure-origin-as-secure=http://10.76.140.17", url], + ["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen", + f"--unsafely-treat-insecure-origin-as-secure={chrome_insecure}", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True ) log_info_with_server("Chromium process restarted successfully.") @@ -1156,8 +1492,9 @@ else: # Launch Chromium with the specified URLs (only if not in configuration mode) if not CONFIGURATION_MODE: - url = "10.76.140.17/iweb_v2/index.php/traceability/production" # pentru cazul in care raspberiul nu are sistem de prezenta - subprocess.Popen(["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen", "--unsafely-treat-insecure-origin-as-secure=http://10.76.140.17", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True) + url = APP_CONFIG.get("chrome_url", "http://10.76.140.17/iweb_v2/index.php/traceability/production") + chrome_insecure = APP_CONFIG.get("chrome_insecure_origin", "http://10.76.140.17") + subprocess.Popen(["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen", f"--unsafely-treat-insecure-origin-as-secure={chrome_insecure}", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True) print("✅ Main application browser launched") else: print("🔧 Configuration mode: Main application browser launch DISABLED") @@ -1246,23 +1583,14 @@ except Exception as e: # Initialize table name/ID print("Initializing device configuration...") -name = "idmasa" +name = APP_CONFIG.get("device_name", "noconfig") logging.info("LED controls initialized") logging.info("Variabila Id Masa A fost initializata ") -# Device name is already loaded during early configuration detection -# Use the existing name variable or reload if needed -if 'name' not in globals(): - try: - with open("./data/idmasa.txt", "r") as f: - name = f.readline().strip() or "noconfig" - print(f"✓ Device name reloaded: {name}") - if not CONFIGURATION_MODE: - log_info_with_server(f"Device name initialized: {name}") - except Exception as e: - print(f"Error reloading device name: {e}") - name = "noconfig" +print(f"✓ Device name set from config: {name}") +if not CONFIGURATION_MODE: + log_info_with_server(f"Device name initialized: {name}") logging.info(name) #clasa reader @@ -1275,7 +1603,8 @@ class Reader(rdm6300.BaseReader): return afisare = time.strftime("%Y-%m-%d&%H:%M:%S") - date = f'https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/{name}/{card.value}/1/{afisare}\n' + _base = APP_CONFIG.get("card_post_base_url", "https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record") + date = f'{_base}/{name}/{card.value}/1/{afisare}\n' info = date if name == "noconfig": led1.on() @@ -1294,7 +1623,8 @@ class Reader(rdm6300.BaseReader): return afisare = time.strftime("%Y-%m-%d&%H:%M:%S") - date = f'https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/{name}/{card.value}/0/{afisare}\n' + _base = APP_CONFIG.get("card_post_base_url", "https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record") + date = f'{_base}/{name}/{card.value}/0/{afisare}\n' info = date if name == "noconfig": led1.off() diff --git a/config.py b/config.py index ff15b94..10282c6 100644 --- a/config.py +++ b/config.py @@ -1,203 +1,273 @@ -# config py este gandit pentru a verifica configurarile raspberiului -# verifica hostname-ul si il afiseaza -# verifica ce nume are masa si o afiseaza -# verifica daca masa este configurata cu sistem de prezenta operator -# verifica daca imprimanta este instalata corespunzator +# WMT Device Configuration Tool +# Reads/writes data/config.txt (INI format). +# On save, bumps [meta] last_synced to NOW so app.py will push an +# update-request to Server_Monitorizare_v2 on next startup. + +import os +import time +import socket +import subprocess +import configparser +from datetime import datetime -import os, time, socket, cups, subprocess -import FreeSimpleGUI as psg import FreeSimpleGUI as sg -from multiprocessing import Process + +CONFIG_PATH = "./data/config.txt" +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- -# import socket -host = socket.gethostname() # luam numele de host -def set_printer(): # importam codul python pentru printer - print("Cheking printer") - # acest segment acceseaza fisierul local idmas.txt si verifica ce informatii sunt acolo - # in mod normal acolo salvam numele mesei care trebuie sa corespunda cu numele imprimantei - f = open("./data/idmasa.txt","r") # deschid fisierul in care am salvat numele printerului - test = f.readlines() # citesc toate liniile - f.close() - # in cazul in care avem fisier corupt il resetam la original +def _get_os_version(): try: - idmasa = test[0] - - except IndexError: - idmasa = "0" - print(idmasa) - # citim lista de printere disponibila - conn = cups.Connection () - printers = conn.getPrinters () - printer_stat=0 - for printer in printers: - print (printer, printers[printer]["device-uri"]) - if printer == idmasa: - printer_stat = 1 - + with open("/etc/os-release") as f: + for line in f: + if line.startswith("PRETTY_NAME="): + return line.split("=", 1)[1].strip().strip('"') + except Exception: + pass + return "unknown" - if printer_stat == 1: - print("Printer is Ok") - else: - print("Printer is not ok") - p_Name = idmasa - print("Installing the new printer") - time.sleep(2) - cmd = "sudo /usr/sbin/lpadmin -p "+idmasa+" -E -v usb://CITIZEN/CT-S310II?serial=00000000 -m CTS310II.ppd" - os.popen(cmd) - time.sleep(2) - print("Printer Was Installed") + +def _get_local_ip(): + try: + h = socket.gethostname() + return socket.gethostbyname(h) + except Exception: + return "127.0.0.1" + + +def _read_config(): + """Return a dict with all device-relevant fields from config.txt.""" + p = configparser.ConfigParser() + p.read(CONFIG_PATH) + return { + "device_name": p.get("device", "name", fallback="notconfig"), + "hostname": p.get("device", "hostname", fallback=socket.gethostname()), + "device_ip": p.get("device", "ip", fallback=_get_local_ip()), + "location": p.get("device", "location", fallback=""), + "info_reviewed_at":p.get("device", "info_reviewed_at", fallback="1970-01-01T00:00:00"), + # preserve other sections verbatim + "chrome_url": p.get("chrome", "chrome_url", fallback=""), + "chrome_local_url": p.get("chrome", "chrome_local_url", fallback=""), + "chrome_insecure_origin":p.get("chrome", "chrome_insecure_origin",fallback=""), + "card_api_base_url": p.get("card_api", "base_url", fallback=""), + "server_log_url": p.get("server", "log_url", fallback=""), + "update_host": p.get("server", "update_host", fallback=""), + "update_user": p.get("server", "update_user", fallback=""), + "internet_check_host": p.get("server", "internet_check_host", fallback=""), + "last_synced": p.get("meta", "last_synced", fallback="1970-01-01T00:00:00"), + } + + +def _write_config(cfg): + """Write all fields back to config.txt and bump last_synced to NOW.""" + p = configparser.ConfigParser() + + p.add_section("chrome") + p.set("chrome", "chrome_url", cfg.get("chrome_url", "")) + p.set("chrome", "chrome_local_url", cfg.get("chrome_local_url", "")) + p.set("chrome", "chrome_insecure_origin",cfg.get("chrome_insecure_origin", "")) + + p.add_section("card_api") + p.set("card_api", "base_url", cfg.get("card_api_base_url", "")) + + p.add_section("server") + p.set("server", "log_url", cfg.get("server_log_url", "")) + p.set("server", "update_host", cfg.get("update_host", "")) + p.set("server", "update_user", cfg.get("update_user", "")) + p.set("server", "internet_check_host", cfg.get("internet_check_host", "")) + + p.add_section("device") + p.set("device", "name", cfg["device_name"]) + p.set("device", "hostname", cfg["hostname"]) + p.set("device", "ip", cfg["device_ip"]) + p.set("device", "location", cfg.get("location", "")) + p.set("device", "info_reviewed_at", cfg.get("info_reviewed_at", "1970-01-01T00:00:00")) + + # Bump last_synced → tells app.py local config is newer → push update request + now_ts = datetime.now().isoformat(timespec="seconds") + p.add_section("meta") + p.set("meta", "last_synced", now_ts) + + os.makedirs("./data", exist_ok=True) + with open(CONFIG_PATH, "w") as f: + f.write("# WMT Application Configuration\n") + f.write(f"# Last updated by config.py: {now_ts}\n\n") + p.write(f) + + # Keep legacy idmasa.txt in sync for older code paths + with open("./data/idmasa.txt", "w") as f: + f.write(cfg["device_name"]) + + print(f"✓ config.txt saved (last_synced={now_ts})") + + +def _set_printer(device_name): + """Configure CUPS printer to match the device/masa name.""" + try: + import cups conn = cups.Connection() printers = conn.getPrinters() - for printer in printers: - print(printer, printers[printer]["device-uri"]) - time.sleep(1) - print("Printerwas seted to "+idmasa+"") - print("Test printer App Closed") -# partea acesata se ocupa de verificarea existentei celor doua fisiere pentru a nu avea erori -# verificam daca exista fisierul care identifica masa -path = './data/idmasa.txt' -isFile = os.path.isfile(path) #verifica existenta fisierului prin bolean Tru/false - -if not isFile: -# print(path)# nu se face nimic pentru ca exista fisierul - - fp = open("./data/idmasa.txt", 'w') # cream fisier - fp.write('noconfig') # scriem in fisier prima line pentru a avea un punct de pornire - fp.close() # inchidem fisierul - -# verificam fisierul de prezenta pe baza acestuia stim daca locul de munca este configurat cu card de prezenta sau nu -path1 = './data/idmasa.txt' #verificare existenta al doilea fisier -isFile = os.path.isfile(path1)# verifica existenta fisierului - -#urmeaza sa citim fisierele pentru a crea cateva variabile -# prima variabila este idmasa -f = open("./data/idmasa.txt","r") # deschid fisierul -name = f.readlines() # citesc toate liniile -f.close() # inchid fisierul -try: - idmasa = name[0]# se verifica daca exista informatie in text - -except IndexError: - idmasa = "noconfig"# daca nu exista informatie in text setam variabila - - -n_config = 0 -#incepem sa definim primele functii din configurare -def notokfunction(): # este functie pentru butonul cancel din formular - global n_config - msg1 = "Id masa a fost actualizat la: "+ idmasa +"" ## pregatim mesajul pentru fereastra pop up - n_config = 1 - msg2 = "Slotul Pentru cartela este configurat by default" # pregatim mesajul pentru fereastra pop up - layout = [[sg.Text(msg1)], [sg.Text(msg2)], [sg.Button("Ok")]] - window = sg.Window("Configurari", layout) - while True: - event, values = window.read() - - if event == "Ok" or event == sg.WIN_CLOSED: - - break - - window.close() -#am inchis functia notok - -#functia pentru butonul ok din formular -def okfunction(): - global n_config - if idmasa == config1: # variabila config 1 este preluata din formular - msg1 = "Masa este setat corect: "+ idmasa +"" # se printeaza mesaj ca nu se actualizeaza id de masa - # print(msg1) - else: - f = open("./data/idmasa.txt","w") # deschidem fisierul config in mod scriere - L = config1 - f.write(L) # actualizam linia cu noua valuare din config - f.close() # inchidem fisierul - msg1 = "Id masa a fost actualizat la: "+ config1 +"" # pregatim mesajul pentru fereastra pop up - n_config = 0 -# - - # definim fereastra pentru ok asemena cu functia notok - layout = [[sg.Text(msg1)], [sg.Output(size=(40, 15))], [sg.Button("Ok")]] - - window = sg.Window("Configurari", layout) - while True: - event, values = window.read() - # End program if user closes window or - # presses the OK button - if event == "Ok" or event == sg.WIN_CLOSED: - - break - if nook == 1: - notokfunction() - n_config = 1 - time.sleep(2) - #asteptam 10 secunde si pornim functia de setare printer - set_printer() - #verificam daca hostul corespunde cu ce este in formular - time.sleep(2) - if host == host_conf: - print("Host name ok") - else: - print("Host name not ok") - time.sleep(2) - print("Update Hostname" ) - cmd = "sudo hostnamectl set-hostname "+host_conf+"" # comanda sa schimbam hostnameul + if device_name in printers: + print(f"Printer '{device_name}' is already configured.") + return + print(f"Installing printer '{device_name}' ...") + cmd = (f"sudo /usr/sbin/lpadmin -p {device_name} -E " + f"-v usb://CITIZEN/CT-S310II?serial=00000000 -m CTS310II.ppd") os.popen(cmd) - print("Os hostname updated") time.sleep(2) - print("System will reboot in 5 seconds") - time.sleep(1) - print("System will reboot in 4 seconds") - time.sleep(1) - print("System will reboot in 3 seconds") - time.sleep(1) - print("System will reboot in 2 seconds") - time.sleep(1) - print("System will reboot in 1 seconds") - time.sleep(3) + print(f"Printer '{device_name}' installed.") + except Exception as e: + print(f"Printer setup skipped: {e}") + + +def _update_hostname(new_hostname): + """Change system hostname if it differs from the current one.""" + current = socket.gethostname() + if current == new_hostname: + print(f"Hostname unchanged: {new_hostname}") + return + print(f"Updating hostname: {current} → {new_hostname}") + subprocess.run(["sudo", "hostnamectl", "set-hostname", new_hostname], check=False) + print("Hostname updated. A reboot is recommended.") + + +def _show_result(title, lines): + """Simple modal popup.""" + layout = [[sg.Text(line)] for line in lines] + [[sg.Button("OK")]] + w = sg.Window(title, layout, modal=True) + while True: + ev, _ = w.read() + if ev in (sg.WIN_CLOSED, "OK"): + break + w.close() + + +# --------------------------------------------------------------------------- +# Main GUI +# --------------------------------------------------------------------------- + +def main(): + cfg = _read_config() + os_ver = _get_os_version() + local_ip = _get_local_ip() + + sg.theme("Dark Grey 9") + sg.set_options(font=("Arial Bold", 15)) + + LABEL_W = 22 + INPUT_W = 38 + + layout = [ + [sg.Text("WMT Device Configuration", font=("Arial Bold", 18), + justification="center", expand_x=True, pad=(0, 12))], + + # ── Editable fields ────────────────────────────────────── + [sg.HorizontalSeparator()], + [sg.Text("── Device Identity ──", font=("Arial Bold", 13), + text_color="#aaaaaa", pad=(0, 6))], + + [sg.Text("Hostname", size=(LABEL_W, 1)), + sg.InputText(cfg["hostname"], key="-HOSTNAME-", size=(INPUT_W, 1))], + + [sg.Text("Loc de Munca / Masa", size=(LABEL_W, 1)), + sg.InputText(cfg["device_name"], key="-MASA-", size=(INPUT_W, 1))], + + [sg.Text("Location", size=(LABEL_W, 1)), + sg.InputText(cfg.get("location", ""), key="-LOCATION-", + size=(INPUT_W, 1), + tooltip="Physical location, e.g. Floor 2, Line A, Masa-01")], + + # ── Read-only system info ───────────────────────────────── + [sg.HorizontalSeparator()], + [sg.Text("── System Info (read-only) ──", font=("Arial Bold", 13), + text_color="#aaaaaa", pad=(0, 6))], + + [sg.Text("OS Version", size=(LABEL_W, 1)), + sg.InputText(os_ver, key="-OS-", size=(INPUT_W, 1), + disabled=True, text_color="#888888")], + + [sg.Text("IP Address", size=(LABEL_W, 1)), + sg.InputText(local_ip, key="-IP-", size=(INPUT_W, 1), + disabled=True, text_color="#888888")], + + [sg.Text("Last Synced", size=(LABEL_W, 1)), + sg.InputText(cfg["last_synced"], key="-SYNC-", size=(INPUT_W, 1), + disabled=True, text_color="#888888")], + + # ── Status output ───────────────────────────────────────── + [sg.HorizontalSeparator()], + [sg.Multiline("", key="-OUT-", size=(62, 5), disabled=True, + autoscroll=True, background_color="#1a1a1a", + text_color="#cccccc", font=("Courier", 12))], + + # ── Buttons ─────────────────────────────────────────────── + [sg.Button("Save & Apply", key="-SAVE-", button_color=("white", "#2980b9"), size=(18, 1)), + sg.Button("Cancel", key="-CANCEL-", size=(12, 1))], + ] + + window = sg.Window("WMT Configuration", layout, size=(720, 540), finalize=True) + + def log(msg): + window["-OUT-"].update(disabled=False) + window["-OUT-"].print(msg) + window["-OUT-"].update(disabled=True) + + while True: + event, values = window.read() + + if event in (sg.WIN_CLOSED, "-CANCEL-"): + break + + if event == "-SAVE-": + new_hostname = values["-HOSTNAME-"].strip() + new_masa = values["-MASA-"].strip() + new_location = values["-LOCATION-"].strip() + + if not new_hostname or not new_masa: + _show_result("Validation Error", + ["Hostname and Loc de Munca / Masa are required."]) + continue + + # Update cfg dict with edited values + cfg["hostname"] = new_hostname + cfg["device_name"] = new_masa + cfg["location"] = new_location + cfg["device_ip"] = local_ip + + # 1. Save config.txt (bumps last_synced → triggers server update request) + try: + _write_config(cfg) + log("✓ config.txt saved. last_synced updated to NOW.") + log(" → app.py will push update request to server on next start.") + except Exception as e: + log(f"✗ Failed to save config.txt: {e}") + + # 2. Update hostname if changed + try: + _update_hostname(new_hostname) + log(f"✓ Hostname: {new_hostname}") + except Exception as e: + log(f"✗ Hostname update error: {e}") + + # 3. Configure printer + try: + _set_printer(new_masa) + log(f"✓ Printer check done for '{new_masa}'.") + except Exception as e: + log(f" Printer: {e}") + + log("") + log("Done. Reboot recommended if hostname changed.") - - window.close() -# incepem initializarea feresteri ptrincipale gui - -sg.theme('dark grey 9') #alegem tema dark grey -psg.set_options(font=('Arial Bold', 16)) # setam fontul -#setarile de layout -layout = [ - [sg.Text('Host_Name', size=(20,1)),sg.InputText(default_text= ""+host+"" , enable_events=False)], - [sg.Text('Loc De Munca', size=(20,1)),sg.InputText(default_text=""+idmasa+"", enable_events=False)], - - [sg.Button('Ok'), sg.Button('Cancel')] -] -# setam window -window = psg.Window('Form', layout, size=(800,190),finalize=True) -# citim si configuram widgetul pentru butoanele 1 si 0 din randul -while True: - - nook = 0 # cream o variabila care sa ne spuna daca a fost setao butonul ok sau nu - event, values = window.read() # citim valorile intr-o lista numita values - host_conf= values[0] # atribuim primul item din lista variabilei Host config - # aceasta variabila o vom folosi pentru a scrie sa nu noul hostname - print(host_conf) - if event == sg.WIN_CLOSED or event == 'Cancel': - nook = 1 # daca se da cancel setam variabila nook la 1 - n_config = 1 # setam variabila n_config la 1 - break - config1 = values[1] # atribuim lui config 1 valuarea din campul Loc de munca care a fost scris cu Id masa - - - # pornim functi care scrie valorile config in fisiere ok function - okfunction() -# si inchidem formularul - - # semnalam ca s-a terminat afisarea formularului - break - - -#inchidem formularul + +if __name__ == "__main__": + main() + window.close() # daca variabila nook este 1 time.sleep(2) diff --git a/libraries.sh b/libraries.sh deleted file mode 100644 index e69de29..0000000