Fix commento

This commit is contained in:
Samuele Locatelli
2026-05-12 17:52:03 +02:00
parent 2198f01425
commit 7387c51631
+396
View File
@@ -0,0 +1,396 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# readParallela v. 3.1.0 12 Ingressi
# - single instance timer
# - invio multiplo x send eventi accodati
# - gestione segnali BLINKING
# - gestione INVERSIONE segnali cv 10-VII-2018
# - gestione FILTRAGGIO segnali brevi cv 23-VII-2018
# - (2.3) gestione 12 bit cv 14-I-2020
# - (2.4) fix ingressi e conf apertura parallela + gestione vari bit filtraggio x nuovi ingressi + update conf con 12 parametri bit SEL 15-I-2020
# - (2.4.8) versione adatta a raspberry PI vecchia generazione (GPIO corto, 8bit)
# - (2.5) Fix (hope) ciclo "wait send to complete", gestione timeout (rety infinito se IO riparte in modo anomalo)
# - (2.5.1) Fix numero versione 18.05.2023
# - (2.5.2) Fix gestione eccezioni con report dettagliato
# - (2.5.3) Fix gestione stringhe e print x python 3.11 in debian 12 / raspberry OS 2025
# - (2.6.0) Aggiunto gestione Redis x code salvate ogni minuto e ricaricate all'avvio 2025.04.17
# - (2.6.1) Cleanup generale vecchia queue post test vari
# - (2.6.2) Fix in global di to_retry in send_coda per evitare problemi
# - (3.0.0) Prima versione code-assisted: riformulazione e ottimizzazione globale programma
# - (3.1.0) Ottimizzazione gestione letture GPIO (sempre code-assisted)
import time
import sys
import os
import logging
import logging.handlers
import threading
import configparser
from datetime import datetime, timezone
from array import array
import redis
import requests
import urllib3
# Disable urllib3 debug logging to keep application logs clean
urllib3.disable_warnings()
logging.getLogger("urllib3").setLevel(logging.WARNING)
# Note: RPi.GPIO is imported inside the class or at runtime to prevent errors on non-Pi systems
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
class ReadParallelaIOB:
def __init__(self, config_path='IOB.cfg'):
self.PROGRAM_NAME = "ReadPar IOB-pi v.3.1.0 (2026)"
self.MAXRETRY = 10
self.MAX_COUNTER_BLINK = 10
# Configuration and State
self.config = configparser
# Hardware Pins (12 inputs)
self.input_pins = [11, 12, 13, 15, 16, 18, 22, 7, 29, 31, 32, 36]
# Internal State Arrays
self.i_counters = array('i', [0] * 12)
self.B_blinking = array('B', [0] * 12)
self.B_previous = array('B', [0] * 12)
self.B_input = array('B', [0] * 12)
self.B_output = array('B', [0] * 12)
self.B_inverting = array('B', [0] * 12)
self.B_filter = array('B', [0] * 12)
self.B_filter_prev = array('B', [0] * 12)
self.B_temp = array('B', [0] * 12)
self.i_filter_counters = array('i', [0] * 12)
# Load configuration after arrays are initialized
self.load_config(config_path)
# Control Variables
self.cont = 0
self.onLine = '1'
self.sending = False
self.timer_busy = False
self.to_enable = False
self.to_short = self.TIMEOUTSHORT
self.to_long = self.TIMEOUTLONG
self.to_retry = self.MAXRETRY
# Redis
self.CodaR = redis.Redis(host='localhost', port=6379, db=0, password='24068Seriate')
self.queue_name = 'IOB'
# Logging
self.setup_logging()
def load_config(self, path):
"""
Loads configuration parameters from the specified .cfg file.
Sets up timing, URL, logging, and bit-specific settings (blinking, inversion, filtering).
"""
config = configparser.RawConfigParser()
if not os.path.exists(path):
print(f"Error: Config file {path} not found.")
sys.exit(1)
config.read(path)
self.idxMacchina = config.get('id', 'idxMacchina')
self.SAMPLETIME = config.getfloat('time', 'SAMPLETIME')
self.TIMEOUTSHORT = config.getfloat('time', 'TIMEOUTSHORT')
self.TIMEOUTLONG = config.getfloat('time', 'TIMEOUTLONG')
self.SENDURLTIME = config.getfloat('time', 'SENDURLTIME')
self.NMAXSEND = config.getint('time', 'NMAXSEND')
self.URLBASE = config.get('web', 'URLBASE')
self.URLENABLED = config.get('web', 'URLENABLED')
self.URLALIVE = config.get('web', 'URLALIVE')
self.URLADV1 = config.get('web', 'URLADV1')
self.LOGFILE = config.get('log', 'LOGFILE')
self.LOGLEVEL = config.get('log', 'LOGLEVEL')
# Load bit settings using loops instead of manual lines
for i in range(12):
self.B_blinking[i] = config.getint('blink', f'bit{i}')
self.MAX_COUNTER_BLINK = config.getint('blink', 'MAX_COUNTER_BLINK')
for i in range(12):
self.B_inverting[i] = config.getint('invert', f'bit{i}')
self.B_filter[i] = config.getint('filter', f'bit{i}')
self.MAX_COUNTER_FILTER = config.getint('filter', 'MAX_COUNTER_FILTER')
def setup_logging(self):
"""
Configures the logging system.
"""
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(name)-8s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename=self.LOGFILE,
filemode='a'
)
self.logQue = logging.getLogger('queue')
self.logSnd = logging.getLogger('sendUrl')
self.logPro = logging.getLogger('program')
def setup_gpio(self):
"""
Initializes the GPIO pins for input.
"""
if GPIO is None:
self.logPro.error("GPIO library not found. Are you on a Raspberry Pi?")
sys.exit(1)
try:
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
for pin in self.input_pins:
GPIO.setup(pin, GPIO.IN)
self.logPro.info("GPIO initialized successfully.")
except Exception as e:
self.logPro.error(f"GPIO Setup Error: {e}")
sys.exit(1)
def rqEnqueue(self, item):
"""
Adds an item to the Redis queue.
"""
self.CodaR.rpush(self.queue_name, item)
def rqDequeue(self):
"""
Removes and returns an item from the Redis queue.
"""
item = self.CodaR.lpop(self.queue_name)
return item.decode('utf-8') if item else None
def rqLen(self):
"""
Returns the current length of the Redis queue.
"""
return self.CodaR.llen(self.queue_name)
def readParallelaFiltrata(self):
"""
Performs the core logic: reads GPIO, applies inversion, filtering,
and blinking, then reconstructs the value as a hex string.
"""
try:
# 1. Efficient GPIO Read
# Using a local reference for speed in loops
gpio_input = GPIO.input
pins = self.input_pins
inverting = self.B_inverting
input_arr = self.B_input
for i in range(12):
# Read and invert logic immediately if required
raw_val = 0 if gpio_input(pins[i]) else 1
if inverting[i]:
raw_val = 1 - raw_val
input_arr[i] = raw_val
# 2. Processing Loop (Filtering & Blinking)
# Pre-caching references to reduce attribute lookups in tight loops
filter_arr = self.B_filter
filter_prev = self.B_filter_prev
filter_counters = self.i_filter_counters
max_filter = self.MAX_COUNTER_FILTER
blinking = self.B_blinking
previous = self.B_previous
output_arr = self.B_output
blink_counters = self.i_counters
for i in range(12):
# Debounce / Filter logic
if filter_arr[i]:
curr_in = input_arr[i]
prev_in = filter_prev[i]
if curr_in != prev_in:
# State change detected
if curr_in == 1:
filter_counters[i] = max_filter if filter_counters[i] == 0 else 0
self.B_temp[i] = 0 if filter_counters[i] == max_filter else 1
else:
filter_counters[i] = max_filter if filter_counters[i] == 0 else 0
self.B_temp[i] = 1 if filter_counters[i] == max_filter else 0
filter_prev[i] = curr_in
input_arr[i] = self.B_temp[i]
else:
# No state change, maintain or decrement counter
if filter_counters[i] > 0:
filter_counters[i] -= 1
self.B_temp[i] = 0 if curr_in == 1 else 1
else:
self.B_temp[i] = 1 if curr_in == 1 else 0
# Update the actual input array with the filtered value
input_arr[i] = self.B_temp[i]
# Blinking Logic
if blinking[i] == 0:
output_arr[i] = input_arr[i]
else:
if previous[i] != input_arr[i]:
previous[i] = input_arr[i]
if input_arr[i] == 1:
output_arr[i] = 1
blink_counters[i] = self.MAX_COUNTER_BLINK
else:
if input_arr[i] == 0 and blink_counters[i] > 0:
blink_counters[i] -= 1
if blink_counters[i] == 0:
output_arr[i] = 0
# 3. Optimized Bitwise Reconstruction
new_value = 0
for i in range(12):
if output_arr[i]:
new_value |= (1 << i)
return hex(new_value)[2:].upper()
except Exception as e:
self.logPro.error(f"Error in readParallelaFiltrata: {e}")
return ''
def accoda(self, value):
try:
dt_eve = datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S%f')[:-3]
self.rqEnqueue(f"{dt_eve}#{value}#{self.cont}")
except Exception as e:
self.logPro.error(f"QUEUE ERROR: {e}")
def svuotaCoda(self):
if self.timer_busy:
return
self.timer_busy = True
try:
if self.rqLen() > 0:
# Check connectivity using requests
try:
res_alive = requests.get(self.URLALIVE, timeout=5)
if res_alive.text == 'OK':
res_enabled = requests.get(self.URLENABLED + self.idxMacchina, timeout=5)
if res_enabled.text == 'OK':
if self.onLine == '0':
self.logPro.info("IOB ONLINE!")
self.onLine = '1'
else:
self.onLine = '0'
else:
self.onLine = '0'
except Exception as e:
self.logPro.error(f"Server Connection Error: {e}")
self.onLine = '0'
if self.onLine == '1' and not self.sending:
self.sending = True
for _ in range(self.NMAXSEND):
if self.rqLen() == 0:
break
resp = self.rqDequeue()
if not resp: break
parts = resp.split("#")
dt_eve, val, cnt = parts[0], parts[1], parts[2]
dt_curr = datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S%f')[:-3]
url = f"{self.URLBASE}{self.idxMacchina}{self.URLADV1}{val}&dtCurr={dt_curr}&dtEve={dt_eve}&cnt={cnt}"
try:
r = requests.get(url, timeout=5)
self.logSnd.info(f"{val} [{cnt}] R:{r.text}")
except Exception as e:
self.logSnd.error(f"Send Error: {e}")
self.sending = False
elif self.sending:
if self.to_retry > 0:
self.to_retry -= 1
self.logPro.info("WAIT active send to complete")
else:
self.sending = False
self.to_retry = self.MAXRETRY
self.logPro.info("END WAIT, reset to_retry")
except Exception as e:
self.logPro.error(f"svuotaCoda Error: {e}")
finally:
self.timer_busy = False
def run(self):
"""
Starts the main execution loop:
1. Initializes GPIO.
2. Spawns a background daemon thread to empty the Redis queue.
3. Enters a loop to sample GPIO inputs, apply filters/blinking, and queue changes.
"""
self.setup_gpio()
# Start background thread for queue emptying
def timer_worker():
while True:
self.svuotaCoda()
time.sleep(self.SENDURLTIME)
threading.Thread(target=timer_worker, daemon=True).start()
old_value = ''
self.logPro.info("-------------------------------")
self.logPro.info(self.PROGRAM_NAME)
self.logPro.info("-------------------------------")
self.logPro.info("Starting main loop")
while True:
try:
time.sleep(self.SAMPLETIME)
value = self.readParallelaFiltrata()
if value != '':
if value != old_value:
self.logQue.info(f"{value} [{self.cont}]")
self.accoda(value)
self.cont = (self.cont + 1) % 10000
self.to_enable = True
self.to_short = self.TIMEOUTSHORT
self.to_long = self.TIMEOUTLONG
old_value = value
# Handle Timeouts
if self.to_enable:
self.to_short -= self.SAMPLETIME
if self.to_short <= 0:
self.logQue.info(f">{value} [{self.cont}]")
self.accoda(value)
self.to_short = self.TIMEOUTSHORT
self.to_enable = False
self.to_long = self.TIMEOUTLONG
self.to_long -= self.SAMPLETIME
if self.to_long <= 0:
self.logQue.info(f">>{value} [{self.cont}]")
self.accoda(value)
self.to_long = self.TIMEOUTLONG
except KeyboardInterrupt:
self.logPro.info("Keyboard interrupt received. Exiting...")
break
except Exception as e:
self.logPro.error(f"Main loop error: {e}")
if __name__ == "__main__":
app = ReadParallelaIOB()
app.run()