509 lines
12 KiB
Markdown
509 lines
12 KiB
Markdown
# Prezenta Work - Modular Architecture Guide
|
|
|
|
## Overview
|
|
|
|
The application has been refactored from a monolithic `app.py` (1334 lines) into a modular structure with separation of concerns. Each module handles a specific responsibility.
|
|
|
|
---
|
|
|
|
## Module Structure
|
|
|
|
### Core Configuration
|
|
**File:** `config_settings.py`
|
|
- **Purpose:** Centralized configuration management
|
|
- **Responsibilities:**
|
|
- Server addresses and credentials
|
|
- File paths and directories
|
|
- Flask configuration
|
|
- Hardware settings
|
|
- Logging configuration
|
|
- Allowed commands list
|
|
- Environment variable loading from `.env`
|
|
|
|
**Key Features:**
|
|
- All settings in one place
|
|
- Environment variable support (can override defaults)
|
|
- Automatic directory creation
|
|
- `.env` file support for sensitive data
|
|
|
|
---
|
|
|
|
### Logging Module
|
|
**File:** `logger_module.py`
|
|
- **Purpose:** Unified logging system
|
|
- **Responsibilities:**
|
|
- Local file logging
|
|
- Remote server notifications
|
|
- Log rotation (10-day retention)
|
|
- Device/table name management
|
|
|
|
**Key Functions:**
|
|
```python
|
|
setup_logging() # Configure logger
|
|
log_with_server() # Log locally + send to server
|
|
send_log_to_server() # Send to monitoring server
|
|
read_masa_name() # Get table/room name
|
|
delete_old_logs() # Cleanup old logs
|
|
```
|
|
|
|
---
|
|
|
|
### Device Module
|
|
**File:** `device_module.py`
|
|
- **Purpose:** Device information management
|
|
- **Responsibilities:**
|
|
- Get hostname and IP address
|
|
- File-based fallback for device info
|
|
- Handle network resolution errors
|
|
|
|
**Key Functions:**
|
|
```python
|
|
get_device_info() # Returns (hostname, device_ip)
|
|
```
|
|
|
|
---
|
|
|
|
### System Initialization
|
|
**File:** `system_init_module.py`
|
|
- **Purpose:** First-run setup and hardware validation
|
|
- **Responsibilities:**
|
|
- System requirements checking
|
|
- Port capability setup (port 80)
|
|
- Hardware interface detection (UART/Serial)
|
|
- GPIO permission setup
|
|
- Network connectivity check
|
|
- Required file creation
|
|
|
|
**Key Functions:**
|
|
```python
|
|
perform_system_initialization() # Main initialization
|
|
check_system_requirements()
|
|
check_port_capabilities()
|
|
check_hardware_interfaces()
|
|
initialize_gpio_permissions()
|
|
check_network_connectivity()
|
|
create_required_files()
|
|
```
|
|
|
|
---
|
|
|
|
### Dependencies Management
|
|
**File:** `dependencies_module.py`
|
|
- **Purpose:** Package installation and verification
|
|
- **Responsibilities:**
|
|
- Wheel file installation
|
|
- Pip package installation
|
|
- Apt system package installation
|
|
- Dependency verification
|
|
|
|
**Key Functions:**
|
|
```python
|
|
check_and_install_dependencies() # Install missing packages
|
|
verify_dependencies() # Verify all packages available
|
|
install_package_from_wheel() # Install from wheel file
|
|
```
|
|
|
|
---
|
|
|
|
### Commands Execution
|
|
**File:** `commands_module.py`
|
|
- **Purpose:** Secure command execution with restrictions
|
|
- **Responsibilities:**
|
|
- Command allowlist enforcement
|
|
- Execution with timeout
|
|
- Logging command results
|
|
- Error handling and reporting
|
|
|
|
**Key Functions:**
|
|
```python
|
|
execute_system_command(command, hostname, device_ip)
|
|
```
|
|
|
|
**Allowed Commands:**
|
|
- `sudo apt update`, `sudo apt upgrade -y`
|
|
- `sudo apt autoremove -y`, `sudo apt autoclean`
|
|
- `sudo reboot`, `sudo shutdown -h now`
|
|
- `df -h`, `free -m`, `uptime`, `systemctl status`
|
|
- `sudo systemctl restart networking/ssh`
|
|
|
|
---
|
|
|
|
### Auto-Update Module
|
|
**File:** `autoupdate_module.py`
|
|
- **Purpose:** Remote application updates
|
|
- **Responsibilities:**
|
|
- Version checking
|
|
- Remote file downloading
|
|
- Backup creation
|
|
- Update verification
|
|
- Device restart scheduling
|
|
|
|
**Key Functions:**
|
|
```python
|
|
perform_auto_update() # Main update process
|
|
get_app_version() # Extract version from app
|
|
check_remote_version() # Check server version
|
|
```
|
|
|
|
**Process:**
|
|
1. Get local app version (from first line: `#App version X.X`)
|
|
2. Connect to update server via SSH
|
|
3. Compare versions
|
|
4. If update available:
|
|
- Create backup
|
|
- Download new files
|
|
- Schedule device restart
|
|
|
|
---
|
|
|
|
### Connectivity Module
|
|
**File:** `connectivity_module.py`
|
|
- **Purpose:** Network monitoring and backup data handling
|
|
- **Responsibilities:**
|
|
- Periodic connectivity checks
|
|
- Fallback data posting
|
|
- Harting server integration
|
|
|
|
**Key Functions:**
|
|
```python
|
|
check_internet_connection() # Periodic connectivity monitoring
|
|
post_backup_data() # Send queued data to Harting server
|
|
```
|
|
|
|
**Features:**
|
|
- Checks internet every 45 minutes
|
|
- Posts queued URLs from `tag.txt`
|
|
- Retry logic for failed posts
|
|
- Automatic cleanup of successful posts
|
|
|
|
---
|
|
|
|
### API Routes Module
|
|
**File:** `api_routes_module.py`
|
|
- **Purpose:** Flask API endpoints
|
|
- **Responsibilities:**
|
|
- Route registration
|
|
- Request handling
|
|
- Response formatting
|
|
|
|
**Key Endpoints:**
|
|
```
|
|
POST /execute_command - Execute allowed system commands
|
|
GET /status - Get device status information
|
|
POST /auto_update - Trigger application update
|
|
```
|
|
|
|
**Key Functions:**
|
|
```python
|
|
create_api_routes(app, hostname, device_ip, local_app_path, local_repo_path)
|
|
```
|
|
|
|
---
|
|
|
|
### RFID Module
|
|
**File:** `rfid_module.py`
|
|
- **Purpose:** RFID reader initialization
|
|
- **Responsibilities:**
|
|
- Reader initialization with multiple device attempts
|
|
- Error handling and troubleshooting
|
|
|
|
**Key Functions:**
|
|
```python
|
|
initialize_rfid_reader() # Initialize RFID reader
|
|
```
|
|
|
|
**Supported Devices:**
|
|
- `/dev/ttyS0` - Raspberry Pi default UART
|
|
- `/dev/ttyAMA0` - Alternative Pi UART
|
|
- `/dev/ttyUSB0` - USB serial adapter
|
|
- `/dev/ttyACM0` - USB CDC ACM device
|
|
|
|
---
|
|
|
|
### Main Application
|
|
**File:** `app_modular.py`
|
|
- **Purpose:** Application orchestration and startup
|
|
- **Responsibilities:**
|
|
- Initialize all modules in sequence
|
|
- Start Flask server
|
|
- Start connectivity monitoring
|
|
- Start RFID reader
|
|
- Handle errors and shutdown
|
|
|
|
**Key Functions:**
|
|
```python
|
|
main() # Application entry point
|
|
initialize_application() # Setup phase
|
|
start_flask_server() # Start HTTP API
|
|
start_connectivity_monitor() # Background monitoring
|
|
start_rfid_reader() # RFID initialization
|
|
```
|
|
|
|
---
|
|
|
|
## Usage
|
|
|
|
### Running the Application
|
|
|
|
```bash
|
|
# Run with default configuration
|
|
python3 app_modular.py
|
|
|
|
# Run with environment variables
|
|
MONITORING_SERVER_HOST=192.168.1.100 FLASK_PORT=8080 python3 app_modular.py
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
Create a `.env` file in the application directory:
|
|
|
|
```env
|
|
# Server Configuration
|
|
MONITORING_SERVER_HOST=rpi-ansible
|
|
MONITORING_SERVER_PORT=80
|
|
AUTO_UPDATE_SERVER_HOST=rpi-ansible
|
|
AUTO_UPDATE_SERVER_USER=pi
|
|
AUTO_UPDATE_SERVER_PASSWORD=your_password
|
|
CONNECTIVITY_CHECK_HOST=10.76.140.17
|
|
|
|
# Flask Configuration
|
|
FLASK_PORT=80
|
|
```
|
|
|
|
### Configuration Files
|
|
|
|
#### Device Configuration
|
|
- **`./data/idmasa.txt`** - Table/room name (used in all logs)
|
|
- **`./data/device_info.txt`** - Cached hostname & IP (fallback)
|
|
- **`./data/tag.txt`** - Harting server URLs for backup posting
|
|
- **`./data/log.txt`** - Application logs (auto-rotated at 10 days)
|
|
|
|
---
|
|
|
|
## Dependency Tree
|
|
|
|
```
|
|
app_modular.py (Main)
|
|
├── config_settings.py (Configuration)
|
|
├── dependencies_module.py (Package Management)
|
|
├── system_init_module.py (Initialization)
|
|
│ └── config_settings.py
|
|
├── device_module.py (Device Info)
|
|
│ └── config_settings.py
|
|
├── logger_module.py (Logging)
|
|
│ ├── config_settings.py
|
|
│ └── External: requests
|
|
├── connectivity_module.py (Network)
|
|
│ ├── config_settings.py
|
|
│ ├── logger_module.py
|
|
│ └── External: requests
|
|
├── commands_module.py (Command Execution)
|
|
│ ├── config_settings.py
|
|
│ └── logger_module.py
|
|
├── autoupdate_module.py (Updates)
|
|
│ ├── config_settings.py
|
|
│ └── logger_module.py
|
|
├── api_routes_module.py (API)
|
|
│ ├── commands_module.py
|
|
│ ├── autoupdate_module.py
|
|
│ ├── logger_module.py
|
|
│ └── External: Flask
|
|
└── rfid_module.py (RFID)
|
|
├── config_settings.py
|
|
└── External: rdm6300
|
|
```
|
|
|
|
---
|
|
|
|
## Migration from Old App
|
|
|
|
The old monolithic `app.py` has been preserved. To use the new modular version:
|
|
|
|
```bash
|
|
# The old app
|
|
python3 app.py
|
|
|
|
# The new modular app
|
|
python3 app_modular.py
|
|
```
|
|
|
|
Both versions can coexist during testing and transition period.
|
|
|
|
---
|
|
|
|
## Benefits of Modular Architecture
|
|
|
|
### ✅ Maintainability
|
|
- Each module has a single responsibility
|
|
- Easy to locate and fix bugs
|
|
- Clear code organization
|
|
|
|
### ✅ Testability
|
|
- Modules can be unit tested independently
|
|
- Easier to mock dependencies
|
|
- Cleaner test structure
|
|
|
|
### ✅ Reusability
|
|
- Modules can be imported and used elsewhere
|
|
- Configuration module can be shared with other apps
|
|
- Logger and connectivity modules are standalone
|
|
|
|
### ✅ Scalability
|
|
- Easy to add new features in separate modules
|
|
- New endpoints can be added without modifying core code
|
|
- Configuration management is centralized
|
|
|
|
### ✅ Readability
|
|
- Smaller files are easier to understand
|
|
- Clear module naming reflects responsibility
|
|
- Better code organization
|
|
|
|
### ✅ Flexibility
|
|
- Easy to swap implementations
|
|
- Configuration can be externalized
|
|
- Environment-specific settings via `.env`
|
|
|
|
---
|
|
|
|
## Adding New Features
|
|
|
|
### Example: Add a New API Endpoint
|
|
|
|
1. Create a new module: `my_feature_module.py`
|
|
2. Implement your logic
|
|
3. Update `api_routes_module.py` to register the route
|
|
4. Test independently
|
|
|
|
### Example: Change Server Address
|
|
|
|
Edit `config_settings.py` or set environment variable:
|
|
|
|
```bash
|
|
MONITORING_SERVER_HOST=new-server.com python3 app_modular.py
|
|
```
|
|
|
|
### Example: Modify Allowed Commands
|
|
|
|
Edit `config_settings.py`:
|
|
|
|
```python
|
|
ALLOWED_COMMANDS = [
|
|
# ... existing commands ...
|
|
"my_custom_command arg1 arg2"
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
Each module follows consistent error handling:
|
|
|
|
1. **Try-except blocks** for external operations
|
|
2. **Logging** of all errors via logger_module
|
|
3. **Graceful degradation** when components fail
|
|
4. **Informative messages** for debugging
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
- **Modular imports** only load necessary dependencies
|
|
- **Lazy loading** of Flask only when available
|
|
- **Daemon threads** for background tasks
|
|
- **Subprocess timeouts** prevent hanging operations
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Port 80 Permission Denied
|
|
```bash
|
|
sudo setcap cap_net_bind_service=ep $(which python3)
|
|
```
|
|
|
|
### RFID Reader Not Found
|
|
```bash
|
|
# Add user to dialout group
|
|
sudo usermod -a -G dialout $USER
|
|
sudo reboot
|
|
```
|
|
|
|
### Cannot Connect to Server
|
|
- Check `MONITORING_SERVER_HOST` in config_settings.py
|
|
- Verify network connectivity with `ping 8.8.8.8`
|
|
- Check firewall rules
|
|
|
|
### Logs Not Sending
|
|
- Verify server endpoint: `http://MONITORING_SERVER_HOST:80/logs`
|
|
- Check network connectivity monitor is running
|
|
- Review log files for error messages
|
|
|
|
---
|
|
|
|
## File Summary
|
|
|
|
| File | Lines | Purpose |
|
|
|------|-------|---------|
|
|
| `app_modular.py` | ~200 | Application entry point & orchestration |
|
|
| `config_settings.py` | ~200 | Configuration management |
|
|
| `logger_module.py` | ~150 | Logging & notifications |
|
|
| `device_module.py` | ~100 | Device information |
|
|
| `system_init_module.py` | ~300 | System initialization |
|
|
| `dependencies_module.py` | ~150 | Package management |
|
|
| `commands_module.py` | ~80 | Command execution |
|
|
| `autoupdate_module.py` | ~200 | Auto-update functionality |
|
|
| `connectivity_module.py` | ~120 | Network monitoring |
|
|
| `api_routes_module.py` | ~150 | Flask routes |
|
|
| `rfid_module.py` | ~80 | RFID initialization |
|
|
| **Total** | **~1530** | **Organized & maintainable** |
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Test the new modular app**: `python3 app_modular.py`
|
|
2. **Verify all features work**: Test `/status`, `/execute_command`, `/auto_update` endpoints
|
|
3. **Update documentation** for your team
|
|
4. **Gradually migrate** from old app to new app
|
|
5. **Consider systemd service** for automatic startup (see below)
|
|
|
|
---
|
|
|
|
## Optional: Systemd Service
|
|
|
|
Create `/etc/systemd/system/prezenta-work.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Prezenta Work Attendance System
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=pi
|
|
WorkingDirectory=/srv/prezenta_work
|
|
ExecStart=/usr/bin/python3 /srv/prezenta_work/app_modular.py
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Then:
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable prezenta-work
|
|
sudo systemctl start prezenta-work
|
|
sudo systemctl status prezenta-work
|
|
```
|
|
|
|
---
|
|
|
|
## Questions or Issues?
|
|
|
|
Refer to individual module documentation or check logs at:
|
|
- **Application logs:** `./data/log.txt`
|
|
- **System logs:** `journalctl -u prezenta-work` (if using systemd)
|