Add config UI interface and update app.py with config card functionality

- Copied config.py with FreeSimpleGUI configuration interface to root folder
- Added load_config() function to launch config UI when card 12886709 is inserted
- Config UI allows setting hostname and work table (Loc De Munca)
- Automatic printer configuration and hostname updates
- System reboot after configuration changes
- Added config.py to version control
This commit is contained in:
RPI User
2025-12-19 14:38:39 +02:00
parent c3a55a89c3
commit 6b199a0e41
12 changed files with 594 additions and 33 deletions

View File

@@ -195,6 +195,32 @@ def post_backup_data():
except Exception as e: except Exception as e:
logging.error(f"Error posting backup data: {e}") logging.error(f"Error posting backup data: {e}")
def load_config():
"""Launch configuration UI when config card is inserted (card ID: 12886709)"""
try:
config_path = "./config.py"
if os.path.exists(config_path):
logging.info("🔧 Launching configuration interface...")
print("🔧 Launching configuration interface...")
# Launch config.py as a subprocess
subprocess.Popen([sys.executable, config_path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
logging.info("✓ Configuration UI launched successfully")
print("✓ Configuration UI launched successfully")
return True
else:
logging.error("Config file not found at ./config.py")
print("✗ Config file not found at ./config.py")
return False
except Exception as e:
logging.error(f"Error launching configuration: {e}")
print(f"✗ Error launching configuration: {e}")
return False
# ============================================================================ # ============================================================================
# RFID READER - Background Thread # RFID READER - Background Thread
# ============================================================================ # ============================================================================
@@ -256,10 +282,12 @@ class RFIDReader(rdm6300.BaseReader):
"""Detect card insertion - just set flag""" """Detect card insertion - just set flag"""
card_id = card.value card_id = card.value
# Special card: device config card (ignore) # Special card: device config card
if card_id == 12886709: if card_id == 12886709:
logging.info(f"🔴 CONFIG CARD {card_id} detected (ignored)") logging.info(f"🔴 CONFIG CARD {card_id} detected - Loading configuration")
print(f"🔴 CONFIG CARD {card_id} detected (ignored)") print(f"🔴 CONFIG CARD {card_id} detected - Loading configuration")
# Call config loading function
load_config()
return return
# IMMEDIATE LED feedback (BEFORE flag, for instant response) # IMMEDIATE LED feedback (BEFORE flag, for instant response)
@@ -305,19 +333,23 @@ def process_card_events(hostname, device_ip):
if card_id is not None: if card_id is not None:
logging.info(f"[Main] Processing CARD INSERTED: {card_id}") logging.info(f"[Main] Processing CARD INSERTED: {card_id}")
# Build API URL (1 = ON/inserted) # Skip posting to Harting API if device is not configured
url = f"{HARTING_API_BASE}/{name}/{card_id}/1/{timestamp}" if name != "noconfig":
# Build API URL (1 = ON/inserted)
url = f"{HARTING_API_BASE}/{name}/{card_id}/1/{timestamp}"
# Try to post # Try to post
if post_to_harting(url): if post_to_harting(url):
logging.info(f"✓ Card event posted to API: {card_id}") logging.info(f"✓ Card event posted to API: {card_id}")
else:
logging.warning(f"✗ Offline: Saving card {card_id} to backup")
try:
with open(TAG_FILE, "a") as f:
f.write(url + "\n")
except Exception as e:
logging.error(f"Failed to save backup: {e}")
else: else:
logging.warning(f"✗ Offline: Saving card {card_id} to backup") logging.debug(f"Device not configured (noconfig). Skipping API post for card {card_id}")
try:
with open(TAG_FILE, "a") as f:
f.write(url + "\n")
except Exception as e:
logging.error(f"Failed to save backup: {e}")
# ALWAYS send log to monitoring server (regardless of API post result) # ALWAYS send log to monitoring server (regardless of API post result)
send_log_to_server(f"Card {card_id} inserted", hostname, device_ip, name) send_log_to_server(f"Card {card_id} inserted", hostname, device_ip, name)
@@ -327,19 +359,23 @@ def process_card_events(hostname, device_ip):
if card_id is not None: if card_id is not None:
logging.info(f"[Main] Processing CARD REMOVED: {card_id}") logging.info(f"[Main] Processing CARD REMOVED: {card_id}")
# Build API URL (0 = OFF/removed) # Skip posting to Harting API if device is not configured
url = f"{HARTING_API_BASE}/{name}/{card_id}/0/{timestamp}" if name != "noconfig":
# Build API URL (0 = OFF/removed)
url = f"{HARTING_API_BASE}/{name}/{card_id}/0/{timestamp}"
# Try to post # Try to post
if post_to_harting(url): if post_to_harting(url):
logging.info(f"✓ Card event posted to API: {card_id}") logging.info(f"✓ Card event posted to API: {card_id}")
else:
logging.warning(f"✗ Offline: Saving card {card_id} to backup")
try:
with open(TAG_FILE, "a") as f:
f.write(url + "\n")
except Exception as e:
logging.error(f"Failed to save backup: {e}")
else: else:
logging.warning(f"✗ Offline: Saving card {card_id} to backup") logging.debug(f"Device not configured (noconfig). Skipping API post for card {card_id}")
try:
with open(TAG_FILE, "a") as f:
f.write(url + "\n")
except Exception as e:
logging.error(f"Failed to save backup: {e}")
# ALWAYS send log to monitoring server (regardless of API post result) # ALWAYS send log to monitoring server (regardless of API post result)
send_log_to_server(f"Card {card_id} removed", hostname, device_ip, name) send_log_to_server(f"Card {card_id} removed", hostname, device_ip, name)
@@ -495,6 +531,73 @@ def cleanup_old_logs(hostname, device_ip, name):
except Exception as e: except Exception as e:
logging.error(f"Error cleaning up logs: {e}") logging.error(f"Error cleaning up logs: {e}")
# ============================================================================
# CHROMIUM LAUNCHER
# ============================================================================
def launch_chromium(name):
"""
Launch Chromium with either production URL or local fallback page
"""
try:
# Debug logging
print(f"DEBUG: launch_chromium called with name='{name}'")
print(f"DEBUG: name type={type(name)}, len={len(name)}")
print(f"DEBUG: name != 'noconfig' = {name != 'noconfig'}")
logging.debug(f"DEBUG: launch_chromium called with name='{name}' (type={type(name).__name__}, len={len(name)})")
# Determine which URL to use based on device configuration
if name != "noconfig":
# Device is configured - use production URL
url = "http://10.76.140.17/iweb_v2/index.php/traceability/production"
print(f"Device configured as '{name}'. Launching production URL: {url}")
logging.info(f"Launching Chromium with production URL for device: {name}")
# Launch Chromium with production URL
print("Starting Chromium with production URL...")
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
)
else:
# Device not configured - use local HTML file
print(f"Device NOT configured (name='noconfig'). Using local HTML.")
local_html_dir = os.path.join(DATA_DIR, "html")
local_html_file = os.path.join(local_html_dir, "Screen.html")
# Create directory if it doesn't exist
os.makedirs(local_html_dir, exist_ok=True)
# Check if local HTML file exists
if not os.path.exists(local_html_file):
print(f"Warning: Local HTML file not found at {local_html_file}")
print(f"Please create the file or copy it from the data/html directory")
logging.warning(f"Local HTML file not found: {local_html_file}")
# Convert to file:// URL for local file
abs_path = os.path.abspath(local_html_file)
url = f"file://{abs_path}"
print(f"Device not configured. Launching local fallback page: {url}")
logging.info(f"Loading Screen.html from: {abs_path}")
# Launch Chromium with local file - different flags for file:// URLs
print("Starting Chromium with local HTML file...")
subprocess.Popen(
["chromium", "--test-type", "--noerrors", "--kiosk", "--start-fullscreen",
"--no-first-run", "--no-default-browser-check", url],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
start_new_session=True
)
print("✓ Chromium launched successfully")
logging.info("Chromium launched successfully")
except Exception as e:
print(f"✗ Failed to launch Chromium: {e}")
logging.error(f"Failed to launch Chromium: {e}")
# ============================================================================ # ============================================================================
# MAIN APPLICATION # MAIN APPLICATION
# ============================================================================ # ============================================================================
@@ -550,7 +653,11 @@ def main():
wifi_thread.start() wifi_thread.start()
print("✓ WiFi monitor started") print("✓ WiFi monitor started")
# Application ready # Launch Chromium browser
print("\nInitializing Chromium launcher...")
time.sleep(2) # Give system time to stabilize
launch_chromium(read_idmasa())
print()
logging.info("RFID Client operational") logging.info("RFID Client operational")
send_log_to_server("RFID Client operational", hostname, device_ip, read_idmasa()) send_log_to_server("RFID Client operational", hostname, device_ip, read_idmasa())
print("✓ RFID Client operational - waiting for cards...") print("✓ RFID Client operational - waiting for cards...")

205
config.py Normal file
View File

@@ -0,0 +1,205 @@
# config py este gandit pentru a verifica configurarile raspberiului
# verifica hostname-ul si il afiseaza
# verifica ce nume are masa si o afiseaza
# verifica daca masa este configurata cu sistem de prezenta operator
# verifica daca imprimanta este instalata corespunzator
import os, time, socket, cups, subprocess
import FreeSimpleGUI as psg
import FreeSimpleGUI as sg
from multiprocessing import Process
# import socket
host = socket.gethostname() # luam numele de host
def set_printer(): # importam codul python pentru printer
print("Cheking printer")
# acest segment acceseaza fisierul local idmas.txt si verifica ce informatii sunt acolo
# in mod normal acolo salvam numele mesei care trebuie sa corespunda cu numele imprimantei
f = open("./data/idmasa.txt","r") # deschid fisierul in care am salvat numele printerului
test = f.readlines() # citesc toate liniile
f.close()
# in cazul in care avem fisier corupt il resetam la original
try:
idmasa = test[0]
except IndexError:
idmasa = "0"
print(idmasa)
# citim lista de printere disponibila
conn = cups.Connection ()
printers = conn.getPrinters ()
printer_stat=0
for printer in printers:
print (printer, printers[printer]["device-uri"])
if printer == idmasa:
printer_stat = 1
if printer_stat == 1:
print("Printer is Ok")
else:
print("Printer is not ok")
p_Name = idmasa
print("Installing the new printer")
time.sleep(2)
cmd = "sudo /usr/sbin/lpadmin -p "+idmasa+" -E -v usb://CITIZEN/CT-S310II?serial=00000000 -m CTS310II.ppd"
os.popen(cmd)
time.sleep(2)
print("Printer Was Installed")
conn = cups.Connection()
printers = conn.getPrinters()
for printer in printers:
print(printer, printers[printer]["device-uri"])
time.sleep(1)
print("Printerwas seted to "+idmasa+"")
print("Test printer App Closed")
# partea acesata se ocupa de verificarea existentei celor doua fisiere pentru a nu avea erori
# verificam daca exista fisierul care identifica masa
path = './data/idmasa.txt'
isFile = os.path.isfile(path) #verifica existenta fisierului prin bolean Tru/false
if not isFile:
# print(path)# nu se face nimic pentru ca exista fisierul
fp = open("./data/idmasa.txt", 'w') # cream fisier
fp.write('noconfig') # scriem in fisier prima line pentru a avea un punct de pornire
fp.close() # inchidem fisierul
# verificam fisierul de prezenta pe baza acestuia stim daca locul de munca este configurat cu card de prezenta sau nu
path1 = './data/idmasa.txt' #verificare existenta al doilea fisier
isFile = os.path.isfile(path1)# verifica existenta fisierului
#urmeaza sa citim fisierele pentru a crea cateva variabile
# prima variabila este idmasa
f = open("./data/idmasa.txt","r") # deschid fisierul
name = f.readlines() # citesc toate liniile
f.close() # inchid fisierul
try:
idmasa = name[0]# se verifica daca exista informatie in text
except IndexError:
idmasa = "noconfig"# daca nu exista informatie in text setam variabila
n_config = 0
#incepem sa definim primele functii din configurare
def notokfunction(): # este functie pentru butonul cancel din formular
global n_config
msg1 = "Id masa a fost actualizat la: "+ idmasa +"" ## pregatim mesajul pentru fereastra pop up
n_config = 1
msg2 = "Slotul Pentru cartela este configurat by default" # pregatim mesajul pentru fereastra pop up
layout = [[sg.Text(msg1)], [sg.Text(msg2)], [sg.Button("Ok")]]
window = sg.Window("Configurari", layout)
while True:
event, values = window.read()
if event == "Ok" or event == sg.WIN_CLOSED:
break
window.close()
#am inchis functia notok
#functia pentru butonul ok din formular
def okfunction():
global n_config
if idmasa == config1: # variabila config 1 este preluata din formular
msg1 = "Masa este setat corect: "+ idmasa +"" # se printeaza mesaj ca nu se actualizeaza id de masa
# print(msg1)
else:
f = open("./data/idmasa.txt","w") # deschidem fisierul config in mod scriere
L = config1
f.write(L) # actualizam linia cu noua valuare din config
f.close() # inchidem fisierul
msg1 = "Id masa a fost actualizat la: "+ config1 +"" # pregatim mesajul pentru fereastra pop up
n_config = 0
#
# definim fereastra pentru ok asemena cu functia notok
layout = [[sg.Text(msg1)], [sg.Output(size=(40, 15))], [sg.Button("Ok")]]
window = sg.Window("Configurari", layout)
while True:
event, values = window.read()
# End program if user closes window or
# presses the OK button
if event == "Ok" or event == sg.WIN_CLOSED:
break
if nook == 1:
notokfunction()
n_config = 1
time.sleep(2)
#asteptam 10 secunde si pornim functia de setare printer
set_printer()
#verificam daca hostul corespunde cu ce este in formular
time.sleep(2)
if host == host_conf:
print("Host name ok")
else:
print("Host name not ok")
time.sleep(2)
print("Update Hostname" )
cmd = "sudo hostnamectl set-hostname "+host_conf+"" # comanda sa schimbam hostnameul
os.popen(cmd)
print("Os hostname updated")
time.sleep(2)
print("System will reboot in 5 seconds")
time.sleep(1)
print("System will reboot in 4 seconds")
time.sleep(1)
print("System will reboot in 3 seconds")
time.sleep(1)
print("System will reboot in 2 seconds")
time.sleep(1)
print("System will reboot in 1 seconds")
time.sleep(3)
window.close()
# incepem initializarea feresteri ptrincipale gui
sg.theme('dark grey 9') #alegem tema dark grey
psg.set_options(font=('Arial Bold', 16)) # setam fontul
#setarile de layout
layout = [
[sg.Text('Host_Name', size=(20,1)),sg.InputText(default_text= ""+host+"" , enable_events=False)],
[sg.Text('Loc De Munca', size=(20,1)),sg.InputText(default_text=""+idmasa+"", enable_events=False)],
[sg.Button('Ok'), sg.Button('Cancel')]
]
# setam window
window = psg.Window('Form', layout, size=(800,190),finalize=True)
# citim si configuram widgetul pentru butoanele 1 si 0 din randul
while True:
nook = 0 # cream o variabila care sa ne spuna daca a fost setao butonul ok sau nu
event, values = window.read() # citim valorile intr-o lista numita values
host_conf= values[0] # atribuim primul item din lista variabilei Host config
# aceasta variabila o vom folosi pentru a scrie sa nu noul hostname
print(host_conf)
if event == sg.WIN_CLOSED or event == 'Cancel':
nook = 1 # daca se da cancel setam variabila nook la 1
n_config = 1 # setam variabila n_config la 1
break
config1 = values[1] # atribuim lui config 1 valuarea din campul Loc de munca care a fost scris cu Id masa
# pornim functi care scrie valorile config in fisiere ok function
okfunction()
# si inchidem formularul
# semnalam ca s-a terminat afisarea formularului
break
#inchidem formularul
window.close()
# daca variabila nook este 1
time.sleep(2)
if n_config == 0:
os.system("sudo reboot now")

View File

@@ -1,2 +1,2 @@
RPI-Device RPI-testDevice
192.168.1.104 192.168.1.104

69
data/html/Screen.html Executable file
View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Device Not Configured</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
overflow: hidden;
background-color: #000;
font-family: Arial, sans-serif;
}
#container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #000;
}
img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
}
#fallback {
display: none;
color: white;
text-align: center;
font-size: 24px;
}
</style>
</head>
<body>
<div id="container">
<img id="mainImage" src="file:///home/pi/Desktop/Prezenta/data/html/harting_background.jpg" alt="Harting Module" />
<div id="fallback">
<h1>Device Not Configured</h1>
<p>Please scan the configuration RFID card</p>
</div>
</div>
<script>
document.getElementById('mainImage').onerror = function() {
console.error('Image failed to load, showing fallback');
document.getElementById('mainImage').style.display = 'none';
document.getElementById('fallback').style.display = 'block';
};
// Log when image loads successfully
document.getElementById('mainImage').onload = function() {
console.log('Image loaded successfully');
};
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

125
data/html/index.html Normal file
View File

@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Device Configuration Required</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100vh;
overflow: hidden;
font-family: Arial, sans-serif;
background-color: #000;
margin: 0;
padding: 0;
}
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #000;
position: relative;
}
.image-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #000;
position: absolute;
top: 0;
left: 0;
}
img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
}
.overlay {
position: absolute;
bottom: 30px;
left: 0;
right: 0;
text-align: center;
background: rgba(0, 0, 0, 0.7);
padding: 20px;
color: white;
font-size: 18px;
z-index: 100;
}
.status {
margin: 10px 0;
font-size: 16px;
}
.time {
font-size: 14px;
color: #ccc;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="image-wrapper">
<img src="file:///home/pi/Desktop/Prezenta/data/html/harting_background.jpg" alt="Harting Module" onerror="handleImageError()">
</div>
<div class="overlay">
<div class="status">
<strong>Device Not Configured</strong>
</div>
<div class="status">
Please scan the configuration RFID card to initialize this device
</div>
<div class="time" id="time"></div>
</div>
</div>
<script>
function updateTime() {
const now = new Date();
document.getElementById('time').innerHTML = now.toLocaleString();
}
function handleImageError() {
console.error('Failed to load image');
const img = document.querySelector('img');
img.alt = 'Image not available';
img.style.display = 'none';
const wrapper = document.querySelector('.image-wrapper');
wrapper.innerHTML = '<div style="color: white; text-align: center;"><h2>Image Loading Failed</h2><p>Please check internet connection</p></div>';
}
// Update time immediately and then every second
updateTime();
setInterval(updateTime, 1000);
// Prevent screensaver/screen blanking
document.addEventListener('mousemove', () => {
// Keep screen active
});
document.addEventListener('keypress', () => {
// Keep screen active
});
</script>
</body>
</html>

BIN
data/html/myimage.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

BIN
data/html/myimage.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

View File

@@ -1 +1 @@
2_15051100_10 noconfig

Binary file not shown.

View File

@@ -0,0 +1,2 @@
https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/2_15051100_10/7758885/1/2025-12-19&09:35:25
https://dataswsibiusb01.sibiusb.harting.intra/RO_Quality_PRD/api/record/2_15051100_10/7758885/0/2025-12-19&09:35:27

View File

@@ -652,6 +652,11 @@ def read_name_from_file():
# 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):
# Skip sending logs if device is not configured
if n_masa == "noconfig":
logging.debug(f"Skipping server log (device not configured): {log_message}")
return
host = hostname host = hostname
device = device_ip device = device_ip
try: try:
@@ -1125,12 +1130,60 @@ def check_internet_connection():
log_info_with_server(f"An error occurred during internet check: {e}") log_info_with_server(f"An error occurred during internet check: {e}")
time.sleep(60) # Retry after 1 minute in case of an error time.sleep(60) # Retry after 1 minute in case of an error
# Function to launch Chromium
def launch_chromium():
"""
Launch Chromium with either production URL or local fallback page
"""
try:
# Determine which URL to use based on device configuration
if name != "noconfig":
# Device is configured - use production URL
url = "http://10.76.140.17/iweb_v2/index.php/traceability/production"
print(f"Device configured as '{name}'. Launching production URL: {url}")
log_info_with_server(f"Launching Chromium with production URL for device: {name}")
else:
# Device not configured - use local HTML file
local_html_dir = "./data/html"
local_html_file = os.path.join(local_html_dir, "Screen.html")
# Create directory if it doesn't exist
os.makedirs(local_html_dir, exist_ok=True)
# Check if local HTML file exists
if not os.path.exists(local_html_file):
print(f"Warning: Local HTML file not found at {local_html_file}")
print(f"Please create the file or copy it from the data/html directory")
logging.warning(f"Local HTML file not found: {local_html_file}")
# Convert to file:// URL for local file
url = f"file://{os.path.abspath(local_html_file)}"
print(f"Device not configured. Launching local fallback page: {url}")
log_info_with_server("Device not configured. Launching local fallback page")
# Launch Chromium with the determined URL - using same parameters as v3
print("Starting Chromium...")
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
)
print("✓ Chromium launched successfully")
log_info_with_server("Chromium launched successfully")
except Exception as e:
print(f"✗ Failed to launch Chromium: {e}")
log_info_with_server(f"Failed to launch Chromium: {e}")
# Start the internet connection check in a separate process # Start the internet connection check in a separate process
internet_check_process = Process(target=check_internet_connection) internet_check_process = Process(target=check_internet_connection)
internet_check_process.start() 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 # Launch Chromium in any situation
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) print("Initializing Chromium launcher...")
time.sleep(2) # Give system time to stabilize
launch_chromium()
info = "0" info = "0"
#function to post info #function to post info