Add .gitignore, update app.py and config.py, remove stale docs
This commit is contained in:
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal 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
|
||||||
@@ -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.
|
|
||||||
@@ -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
472
app.py
@@ -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
442
config.py
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user