v2.7: Fixed auto-update path detection for case-sensitive file systems

- Replaced hardcoded paths with dynamic path detection using __file__
- Auto-update now works regardless of folder case (prezenta vs Prezenta)
- Added comprehensive dependency management documentation
- Enhanced port 80 capability setup script
- Updated system packages repository structure
- Fixed path resolution for Files/reposytory and system_packages directories
This commit is contained in:
2025-08-14 15:51:29 +03:00
parent 0b9419c6d2
commit 6975e18ed2
48 changed files with 2766 additions and 121 deletions

189
Files/oldapp.py Normal file
View File

@@ -0,0 +1,189 @@
import rdm6300
import os, time
import logging
from gpiozero import OutputDevice
from multiprocessing import Process
import requests
import subprocess
import urllib3
import threading
import urllib.parse
from datetime import datetime, timedelta # Import datetime and timedelta
urllib3.disable_warnings()
# Configure logging
logging.basicConfig(filename='/home/pi/Desktop/Prezenta/data/log.txt', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def delete_old_logs():
log_dir = '/home/pi/Desktop/Prezenta/data/'
log_file = 'log.txt'
log_path = os.path.join(log_dir, log_file)
if os.path.exists(log_path):
file_mod_time = datetime.fromtimestamp(os.path.getmtime(log_path))
if datetime.now() - file_mod_time > timedelta(days=10):
os.remove(log_path)
logging.info("Deleted old log file: %s", log_file)
else:
logging.info("Log file is not older than 10 days: %s", log_file)
else:
logging.info("Log file does not exist: %s", log_file)
# Call the function to delete old logs
delete_old_logs()
def config():
import config
def post_backup_data():
logging.info("Reading backup data from tag.txt...")
try:
with open("./data/tag.txt", "r") as file:
lines = file.readlines()
remaining_lines = lines[:]
for line in lines:
line = line.strip()
if line:
logging.info(f"Posting backup data: {line}")
try:
response = requests.post(line, verify=False, timeout=3)
logging.info("Request sent, awaiting response...")
response.raise_for_status() # Raise an error for bad status codes
logging.info("Data posted successfully: %s", response.text)
remaining_lines.remove(line + "\n")
except requests.exceptions.Timeout:
logging.warning("Request timed out.")
break
except requests.exceptions.RequestException as e:
logging.error("An error occurred: %s", e)
break
with open("./data/tag.txt", "w") as file:
file.writelines(remaining_lines)
logging.info("Backup data updated.")
except FileNotFoundError:
logging.warning("No backup file found.")
except Exception as e:
logging.error("An error occurred while reading the backup file: %s", e)
def check_internet_connection():
hostname = "10.76.140.17" # "https://google.com"
cmd_block_wifi = 'sudo rfkill block wifi'
cmd_unblock_wifi = 'sudo rfkill unblock wifi'
logging.info('Internet connection check loaded')
delete_old_logs()
def manage_wifi_connection():
if var1 == 0:
logging.info("Internet is up! Waiting 45 minutes.")
post_backup_data()
time.sleep(2700)
else:
os.system(cmd_block_wifi)
logging.info('System is rebooting WiFi, please wait until it finishes the job 20 minutes')
time.sleep(1200) # 20 minutes
os.system(cmd_unblock_wifi)
while True:
response = os.system("ping -c 1 " + hostname)
if response == 0:
var1 = 0
manage_wifi_connection()
else:
var1 = 1
manage_wifi_connection()
# Start the internet connection check in a separate process
internet_check_process = Process(target=check_internet_connection)
internet_check_process.start()
url = "10.76.140.17/iweb_v2/index.php/traceability/production" # pentru cazul in care raspberiul nu are sistem de prezenta
# Launch Chromium with the specified URLs
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)
info = "0"
def post_info(info):
logging.info("Starting to post data...")
info1 = info.strip() # Remove any leading/trailing whitespace, including newlines
try:
response = requests.post(info1, verify=False, timeout=3)
logging.info("Request sent, awaiting response...")
response.raise_for_status() # Raise an error for bad status codes
logging.info("Data posted successfully: %s", response.text)
except requests.exceptions.Timeout:
logging.warning("Request timed out. Saving data to backup file.")
with open("./data/tag.txt", "a") as file: # Open in append mode
file.write(info)
logging.info("Value %s was saved to tag.txt", info)
except requests.exceptions.RequestException as e:
logging.error("An error occurred: %s", e)
with open("./data/tag.txt", "a") as file: # Open in append mode
file.write(info)
logging.info("Value %s was saved to tag.txt", info)
def post_info_thread(info):
thread = threading.Thread(target=post_info, args=(info,))
thread.start()
led1 = OutputDevice(23)
led2 = OutputDevice(24)
name = "idmasa"
logging.info("Variabila Id Masa A fost initializata ")
f = open("./data/idmasa.txt", "r") # deschid fisierul
name = f.readline().strip() # citesc toate liniile
f.close() # inchid fisierul
logging.info(name)
class Reader(rdm6300.BaseReader):
global info
def card_inserted(self, card):
if card.value == 12886709:
logging.info("Inserting Config Card")
config()
return
afisare = time.strftime("%Y-%m-%d&%H:%M:%S")
date = 'https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/' + name + '/' + str(card.value) + "/1/" + afisare + "\n"
info = date
if name == "noconfig":
led1.on()
logging.info(info)
logging.info(f"card inserted {card}")
else:
logging.info(info)
post_info_thread(info)
led1.on()
logging.info(f"card inserted {card}")
logging.info(f"card removed {card}")
def card_removed(self, card):
if card.value == 12886709:
logging.info("Removing Config card")
return
# config()
afisare=time.strftime("%Y-%m-%d&%H:%M:%S")
date='https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/'+name+'/'+str(card.value)+"/0/"+afisare+"\n"
info = date
if name == "noconfig":
led1.of()
logging.info(info)
logging.info(f"card card removed {card}")
else:
logging.info(info)
post_info_thread(info)
led1.off()
logging.info(f"card removed {card}")
logging.info(f"card removed {card}")
try:
r = Reader('/dev/ttyS0')
except:
r = Reader('/dev/ttyAMA0')
r.start()

0
Files/reposytory/FreeSimpleGUI-5.0.0-py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/Harting/HA_MWG_new_cert.crt Executable file → Normal file
View File

0
Files/reposytory/Harting/HartingIssuingCA1.crt Executable file → Normal file
View File

0
Files/reposytory/Harting/HartingRootCA.crt Executable file → Normal file
View File

View File

0
Files/reposytory/LinuxPOS-Driver/Readme_POS-CUPS.txt Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

0
Files/reposytory/LinuxPOS-Driver/ctzpos.deb Executable file → Normal file
View File

0
Files/reposytory/PyAutoGUI-0.9.54-py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/WebPrintClient/wcpp.deb Executable file → Normal file
View File

View File

0
Files/reposytory/func_timeout-4.3.5-py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/pynput-1.7.6-py2.py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/pyserial-3.5-py2.py3-none-any.whl Executable file → Normal file
View File

Binary file not shown.

Binary file not shown.

0
Files/reposytory/python3_xlib-0.15-py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/python_xlib-0.31-py2.py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/rdm6300-0.1.1-py3-none-any.whl Executable file → Normal file
View File

0
Files/reposytory/six-1.16.0-py2.py3-none-any.whl Executable file → Normal file
View File

View File

Binary file not shown.

186
README_DEPENDENCIES.md Normal file
View File

@@ -0,0 +1,186 @@
# 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.

Binary file not shown.

1337
app.py

File diff suppressed because it is too large Load Diff

28
cert.sh
View File

@@ -1,28 +0,0 @@
#!/bin/bash
# Instalare certificate
echo -e "\e[1;32m Copiem si instalam certificatele Harting \e[0m"
sudo mkdir /usr/local/share/ca-certificates/Harting
sudo cp -R /home/pi/Desktop/Prezenta/Files/reposytory/Harting/ /usr/local/share/ca-certificates/Harting/
sudo update-ca-certificates
sleep 3
echo -e "\e[1;32m Instalam Libnss3-tool ca sa actualizam certificatele in Browser \e[0m"
sudo apt install libnss3-tools
echo -e "\e[1;32m Libnss3-tools este instalat \e[0m"
echo -e "\e[1;32m Cream baza de date pentru certutil \e[0m"
#sudo mkdir -p $HOME/.pki/nssdb
#sudo certutil -d $HOME/.pki/nssdb -N --empty-password
echo -e "\e[1;32m Baza de date a fost creata \e[0m"
echo -e "\e[1;32m Actualizam Certificate \e[0m"
certutil -d sql:$HOME/.pki/nssdb -A -t "CT,C,C" -n HA_MWG_new_cert -i /etc/ssl/certs/HA_MWG_new_cert.pem
echo "cert 1 ok"
sleep 3
certutil -d sql:$HOME/.pki/nssdb -A -t "CT,C,C" -n HartingIssuingCA1.pem -i /etc/ssl/certs/HartingIssuingCA1.pem
echo "cert 2 Ok"
sleep 3
certutil -d sql:$HOME/.pki/nssdb -A -t "CT,C,C" -n HartingRootCA.pem -i /etc/ssl/certs/HartingRootCA.pem
echo "cert 3 ok"
echo -e "\e[1;32m Certificatele au fost instalate \e[0m"
sleep 3
certutil -d sql:$HOME/.pki/nssdb -L
sleep 3

28
config.py Executable file → Normal file
View File

@@ -62,16 +62,14 @@ isFile = os.path.isfile(path) #verifica existenta fisierului prin bolean Tru/fal
if not isFile:
# print(path)# nu se face nimic pentru ca exista fisierul
#else:
fp = open('./data/idmasa.txt', 'w') # cream fisier
fp.write('no_config') # scriem in fisier prima line pentru a avea un punct de pornire
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
# print("created file")
# 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
#print(path)
#urmeaza sa citim fisierele pentru a crea cateva variabile
# prima variabila este idmasa
@@ -82,16 +80,15 @@ try:
idmasa = name[0]# se verifica daca exista informatie in text
except IndexError:
idmasa = "Initial"# daca nu exista informatie in text setam variabila
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)
@@ -107,6 +104,7 @@ def notokfunction(): # este functie pentru butonul cancel din formular
#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)
@@ -116,7 +114,7 @@ def okfunction():
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
@@ -132,6 +130,7 @@ def okfunction():
break
if nook == 1:
notokfunction()
n_config = 1
time.sleep(2)
#asteptam 10 secunde si pornim functia de setare printer
set_printer()
@@ -177,6 +176,7 @@ window = psg.Window('Form', layout, size=(800,190),finalize=True)
# citim si configuram widgetul pentru butoanele 1 si 0 din randul
while True:
nook = 0 # cream o variabila care sa ne spuna daca a fost setao butonul ok sau nu
event, values = window.read() # citim valorile intr-o lista numita values
host_conf= values[0] # atribuim primul item din lista variabilei Host config
@@ -184,6 +184,7 @@ while True:
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
@@ -200,7 +201,8 @@ while True:
window.close()
# daca variabila nook este 1
time.sleep(2)
os.system("sudo reboot now")
if n_config == 0:
os.system("sudo reboot now")

2
data/device_info.txt Normal file
View File

@@ -0,0 +1,2 @@
RPI-ansible
127.0.1.1

View File

@@ -1 +1 @@
not_config
2_15051100_10

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/notconfig/7955261/1/2025-05-28&16:37:20
https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/notconfig/7955261/0/2025-05-28&16:37:29

3
libraries.sh Normal file → Executable file
View File

@@ -11,4 +11,7 @@ pip3 install /home/pi/Desktop/Prezenta/Files/reposytory/rdm6300-0.1.1-py3-none-a
pip3 install /home/pi/Desktop/Prezenta/Files/reposytory/typing_extensions-4.11.0-py3-none-any.whl -f ./ --no-index --no-deps --break-system-packages
pip3 install /home/pi/Desktop/Prezenta/Files/reposytory/FreeSimpleGUI-5.0.0-py3-none-any.whl -f ./ --no-index --no-deps --break-system-packages
pip3 install /home/pi/Desktop/Prezenta/Files/reposytory/func_timeout-4.3.5-py3-none-any.whl -f ./ --no-index --no-deps --break-system-packages
sudo chmod a+r /home/pi/Desktop/Aiohttp/python3-aiohttp.deb
sudo apt install /home/pi/Desktop/Aiohttp/python3-aiohttp.deb
echo -e "\e[1;32m Librariile au fost instalate cu succes \e[0m"

31
setup_port_capability.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Grant the Python executable the capability to bind to privileged ports (like port 80)
# This is safer than running the entire application as root
echo "Setting up Python to bind to privileged ports..."
# Get the Python executable path and resolve symbolic links
PYTHON_PATH=$(which python3)
REAL_PYTHON_PATH=$(readlink -f "$PYTHON_PATH")
echo "Python path: $PYTHON_PATH"
echo "Real Python path: $REAL_PYTHON_PATH"
# Set the capability on the real executable
sudo setcap 'cap_net_bind_service=+ep' "$REAL_PYTHON_PATH"
if [ $? -eq 0 ]; then
echo "✓ Successfully granted port binding privileges to Python"
echo "Your Flask app can now bind to port 80 without running as root"
# Verify the capability was set
echo "Verifying capability:"
getcap "$REAL_PYTHON_PATH"
else
echo "✗ Failed to set capability"
echo "Make sure you have libcap2-bin installed: sudo apt install libcap2-bin"
fi
echo ""
echo "Note: After setting this capability, you can change your Flask app to use port 80"
echo "Change the port from 5000 to 80 in your app.py file"