Add .gitignore, update app.py and config.py, remove stale docs

This commit is contained in:
2026-04-23 16:04:35 +03:00
parent c6dcdc068d
commit 35fb6b93ad
6 changed files with 677 additions and 531 deletions

20
.gitignore vendored Normal file
View File

@@ -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

View File

@@ -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.

View File

@@ -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

472
app.py
View File

@@ -129,8 +129,258 @@ except ImportError as e:
def jsonify(data): def jsonify(data):
return data return data
import configparser
import json 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) # Early configuration mode detection (before heavy initialization)
def early_launch_configuration_mode(): def early_launch_configuration_mode():
""" """
@@ -139,21 +389,23 @@ def early_launch_configuration_mode():
try: try:
print("🔧 Configuration mode detected - launching Screen.html in Chromium") print("🔧 Configuration mode detected - launching Screen.html in Chromium")
# Get absolute path to Screen.html # Path to Screen.html relative to the WMT working directory
current_dir = os.path.dirname(os.path.abspath(__file__)) screen_html_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Files", "Screen.html")
screen_html_path = os.path.join(current_dir, "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): if not os.path.exists(screen_html_path):
print(f"❌ Screen.html not found at: {screen_html_path}") print(f"❌ Screen.html not found at: {screen_html_path}")
return False return False
print(f"📄 Loading Screen.html from: {screen_html_path}") print(f"📄 Loading Screen.html from: {screen_html_path}")
# Terminate any existing Chromium processes # Terminate any existing Chromium processes
chromium_process_name = "chromium" chromium_process_name = "chromium"
# Local log only in configuration mode # Local log only in configuration mode
logging.info("Refreshing Chromium process (configuration mode)") logging.info("Refreshing Chromium process (configuration mode)")
try: try:
subprocess.run(["pkill", "-f", chromium_process_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.run(["pkill", "-f", chromium_process_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(5) # Wait for processes to terminate time.sleep(5) # Wait for processes to terminate
@@ -168,11 +420,12 @@ def early_launch_configuration_mode():
except Exception as e: except Exception as e:
logging.error(f"Failed to refresh Chromium process (configuration mode): {e}") logging.error(f"Failed to refresh Chromium process (configuration mode): {e}")
return False return False
print("✅ Configuration mode launched successfully") print("✅ Configuration mode launched successfully")
print(" Network connectivity checks disabled") print(" Network connectivity checks disabled")
print(" Server status messages disabled") print(" Server status messages disabled")
print("🔧 Device running in configuration mode") 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 return True
except FileNotFoundError: except FileNotFoundError:
@@ -189,24 +442,23 @@ print("CONFIGURATION MODE DETECTION")
print("=" * 60) print("=" * 60)
try: try:
with open("./data/idmasa.txt", "r") as f: name = APP_CONFIG.get("device_name", "noconfig")
name = f.readline().strip() or "noconfig"
print(f"✓ Device name loaded: {name}") print(f"✓ Device name loaded: {name}")
# Check if device is in configuration mode # Check if device is in configuration mode
if name.lower() == "notconfig": if name.lower() == "notconfig":
print("🔧 Device configured for setup mode (notconfig)") print("🔧 Device configured for setup mode (notconfig)")
# Launch configuration mode # Launch configuration mode
if early_launch_configuration_mode(): if early_launch_configuration_mode():
print("🚀 Configuration mode active - application will run in setup mode") print("🚀 Configuration mode active - application will run in setup mode")
print("⚠️ Network connectivity checks are DISABLED") print("⚠️ Network connectivity checks are DISABLED")
print("⚠️ Server status messages 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 # Set global flag for configuration mode
CONFIGURATION_MODE = True CONFIGURATION_MODE = True
print("🔧 Configuration mode activated - continuing with RFID reader initialization") print("🔧 Configuration mode activated - continuing with RFID reader initialization")
print("🔧 Network and server monitoring will remain disabled") print("🔧 Network and server monitoring will remain disabled")
else: else:
@@ -215,21 +467,9 @@ try:
else: else:
print("✅ Device in normal operation mode") print("✅ Device in normal operation mode")
CONFIGURATION_MODE = False 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: except Exception as e:
print(f"Error reading idmasa.txt: {e}") print(f"Error in configuration mode detection: {e}")
name = "noconfig" name = "noconfig"
CONFIGURATION_MODE = False CONFIGURATION_MODE = False
@@ -305,7 +545,6 @@ def check_system_requirements():
# 2. Check required files and create defaults if missing # 2. Check required files and create defaults if missing
required_files = { required_files = {
'./data/idmasa.txt': 'noconfig',
'./data/log.txt': '', './data/log.txt': '',
'./data/tag.txt': '', './data/tag.txt': '',
'./data/device_info.txt': 'unknown-device\n127.0.0.1\n' './data/device_info.txt': 'unknown-device\n127.0.0.1\n'
@@ -609,11 +848,23 @@ def get_device_info():
except Exception as file_error: except Exception as file_error:
print(f"Could not load from file: {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") print("All methods failed - Using default values")
hostname = hostname or "unknown-device" hostname = hostname or "unknown-device"
device_ip = "127.0.0.1" device_ip = "127.0.0.1"
# Try to save these default values for next time # Try to save these default values for next time
try: try:
os.makedirs("./data", exist_ok=True) os.makedirs("./data", exist_ok=True)
@@ -622,7 +873,7 @@ def get_device_info():
print(f"Saved fallback values to {config_file}") print(f"Saved fallback values to {config_file}")
except Exception as e: except Exception as e:
print(f"Could not save fallback values: {e}") print(f"Could not save fallback values: {e}")
return hostname, device_ip return hostname, device_ip
# Perform system initialization (first run setup) # Perform system initialization (first run setup)
@@ -652,29 +903,42 @@ def delete_old_logs():
else: else:
log_info_with_server(f"Log file does not exist: {log_file}") 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(): def read_name_from_file():
try: try:
with open("./data/idmasa.txt", "r") as file: n_masa = APP_CONFIG.get("device_name", "")
n_masa = file.readline().strip() if n_masa:
return n_masa return n_masa
except FileNotFoundError: except Exception:
logging.error("File ./data/idmasa.txt not found.") pass
return "unknown" 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 # 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): def send_log_to_server(log_message, n_masa, hostname, device_ip):
host = hostname
device = device_ip
try: try:
log_data = { log_data = {
"hostname": str(host), "hostname": str(hostname),
"device_ip": str(device), "device_ip": str(device_ip),
"nume_masa": str(n_masa), "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 print(log_data) # Debugging: Print log_data to verify its contents
response = requests.post(server_url, json=log_data, timeout=5) response = requests.post(server_url, json=log_data, timeout=5)
response.raise_for_status() 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) logging.error("Failed to send log to server: %s", e)
# Wrapper for logging.info to also send logs to the server Monitorizare APP # Wrapper for logging.info to also send logs to the server Monitorizare APP
def log_info_with_server(message): 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 formatted_message = f"{message} (n_masa: {n_masa})" # Format the message
logging.info(formatted_message) # Log the formatted message logging.info(formatted_message) # Log the formatted message
@@ -706,6 +970,7 @@ def execute_system_command(command):
allowed_commands = [ allowed_commands = [
"sudo apt update", "sudo apt update",
"sudo apt upgrade -y", "sudo apt upgrade -y",
"sudo apt update && sudo apt upgrade -y", # Combined update and upgrade
"sudo apt autoremove -y", "sudo apt autoremove -y",
"sudo apt autoclean", "sudo apt autoclean",
"sudo reboot", "sudo reboot",
@@ -811,9 +1076,9 @@ if FLASK_AVAILABLE:
Checks version, downloads newer files if available, and restarts the device Checks version, downloads newer files if available, and restarts the device
""" """
try: try:
# Configuration # Configuration (read from APP_CONFIG, falling back to hardcoded defaults)
SERVER_HOST = "rpi-ansible" SERVER_HOST = APP_CONFIG.get("update_host", "rpi-ansible")
SERVER_USER = "pi" SERVER_USER = APP_CONFIG.get("update_user", "pi")
SERVER_PASSWORD = "Initial01!" SERVER_PASSWORD = "Initial01!"
SERVER_APP_PATH = "/home/pi/Desktop/prezenta/app.py" SERVER_APP_PATH = "/home/pi/Desktop/prezenta/app.py"
SERVER_REPO_PATH = "/home/pi/Desktop/prezenta/Files/reposytory" SERVER_REPO_PATH = "/home/pi/Desktop/prezenta/Files/reposytory"
@@ -990,6 +1255,76 @@ sudo reboot
except: except:
pass 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(): def start_command_server():
""" """
Start the Flask server with enhanced port handling and fallback Start the Flask server with enhanced port handling and fallback
@@ -1100,7 +1435,7 @@ def post_backup_data():
# Function to check internet connection # Function to check internet connection
def 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_block_wifi = 'sudo rfkill block wifi'
cmd_unblock_wifi = 'sudo rfkill unblock wifi' cmd_unblock_wifi = 'sudo rfkill unblock wifi'
log_info_with_server('Internet connection check loaded') log_info_with_server('Internet connection check loaded')
@@ -1133,10 +1468,11 @@ def check_internet_connection():
time.sleep(5) # Wait for processes to terminate time.sleep(5) # Wait for processes to terminate
# Relaunch Chromium # 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( subprocess.Popen(
["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen", ["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen",
"--unsafely-treat-insecure-origin-as-secure=http://10.76.140.17", url], f"--unsafely-treat-insecure-origin-as-secure={chrome_insecure}", url],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True
) )
log_info_with_server("Chromium process restarted successfully.") 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) # Launch Chromium with the specified URLs (only if not in configuration mode)
if not 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 url = APP_CONFIG.get("chrome_url", "http://10.76.140.17/iweb_v2/index.php/traceability/production")
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) 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") print("✅ Main application browser launched")
else: else:
print("🔧 Configuration mode: Main application browser launch DISABLED") print("🔧 Configuration mode: Main application browser launch DISABLED")
@@ -1246,23 +1583,14 @@ except Exception as e:
# Initialize table name/ID # Initialize table name/ID
print("Initializing device configuration...") print("Initializing device configuration...")
name = "idmasa" name = APP_CONFIG.get("device_name", "noconfig")
logging.info("LED controls initialized") logging.info("LED controls initialized")
logging.info("Variabila Id Masa A fost initializata ") logging.info("Variabila Id Masa A fost initializata ")
# Device name is already loaded during early configuration detection print(f"✓ Device name set from config: {name}")
# Use the existing name variable or reload if needed if not CONFIGURATION_MODE:
if 'name' not in globals(): log_info_with_server(f"Device name initialized: {name}")
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"
logging.info(name) logging.info(name)
#clasa reader #clasa reader
@@ -1275,7 +1603,8 @@ class Reader(rdm6300.BaseReader):
return return
afisare = time.strftime("%Y-%m-%d&%H:%M:%S") 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 info = date
if name == "noconfig": if name == "noconfig":
led1.on() led1.on()
@@ -1294,7 +1623,8 @@ class Reader(rdm6300.BaseReader):
return return
afisare = time.strftime("%Y-%m-%d&%H:%M:%S") 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 info = date
if name == "noconfig": if name == "noconfig":
led1.off() led1.off()

442
config.py
View File

@@ -1,203 +1,273 @@
# config py este gandit pentru a verifica configurarile raspberiului # WMT Device Configuration Tool
# verifica hostname-ul si il afiseaza # Reads/writes data/config.txt (INI format).
# verifica ce nume are masa si o afiseaza # On save, bumps [meta] last_synced to NOW so app.py will push an
# verifica daca masa este configurata cu sistem de prezenta operator # update-request to Server_Monitorizare_v2 on next startup.
# verifica daca imprimanta este instalata corespunzator
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 import FreeSimpleGUI as sg
from multiprocessing import Process
CONFIG_PATH = "./data/config.txt"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
# import socket def _get_os_version():
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
try: try:
idmasa = test[0] with open("/etc/os-release") as f:
for line in f:
except IndexError: if line.startswith("PRETTY_NAME="):
idmasa = "0" return line.split("=", 1)[1].strip().strip('"')
print(idmasa) except Exception:
# citim lista de printere disponibila pass
conn = cups.Connection () return "unknown"
printers = conn.getPrinters ()
printer_stat=0
for printer in printers:
print (printer, printers[printer]["device-uri"])
if printer == idmasa:
printer_stat = 1
if printer_stat == 1:
print("Printer is Ok") def _get_local_ip():
else: try:
print("Printer is not ok") h = socket.gethostname()
p_Name = idmasa return socket.gethostbyname(h)
print("Installing the new printer") except Exception:
time.sleep(2) return "127.0.0.1"
cmd = "sudo /usr/sbin/lpadmin -p "+idmasa+" -E -v usb://CITIZEN/CT-S310II?serial=00000000 -m CTS310II.ppd"
os.popen(cmd)
time.sleep(2) def _read_config():
print("Printer Was Installed") """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() conn = cups.Connection()
printers = conn.getPrinters() printers = conn.getPrinters()
for printer in printers: if device_name in printers:
print(printer, printers[printer]["device-uri"]) print(f"Printer '{device_name}' is already configured.")
time.sleep(1) return
print("Printerwas seted to "+idmasa+"") print(f"Installing printer '{device_name}' ...")
print("Test printer App Closed") cmd = (f"sudo /usr/sbin/lpadmin -p {device_name} -E "
# partea acesata se ocupa de verificarea existentei celor doua fisiere pentru a nu avea erori f"-v usb://CITIZEN/CT-S310II?serial=00000000 -m CTS310II.ppd")
# 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
os.popen(cmd) os.popen(cmd)
print("Os hostname updated")
time.sleep(2) time.sleep(2)
print("System will reboot in 5 seconds") print(f"Printer '{device_name}' installed.")
time.sleep(1) except Exception as e:
print("System will reboot in 4 seconds") print(f"Printer setup skipped: {e}")
time.sleep(1)
print("System will reboot in 3 seconds")
time.sleep(1) def _update_hostname(new_hostname):
print("System will reboot in 2 seconds") """Change system hostname if it differs from the current one."""
time.sleep(1) current = socket.gethostname()
print("System will reboot in 1 seconds") if current == new_hostname:
time.sleep(3) 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() 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:
if __name__ == "__main__":
nook = 0 # cream o variabila care sa ne spuna daca a fost setao butonul ok sau nu main()
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
window.close() window.close()
# daca variabila nook este 1 # daca variabila nook este 1
time.sleep(2) time.sleep(2)

View File