diff --git a/IOB-PI/readParallela.py b/IOB-PI/readParallela.py index 0982a23..076c2ca 100644 --- a/IOB-PI/readParallela.py +++ b/IOB-PI/readParallela.py @@ -1,787 +1,323 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# readParallela v. 2.6.2 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 - - import time import sys -from datetime import datetime -import urllib -import urllib.request -import configparser -import os, sys +import os import logging import logging.handlers import threading +import configparser +from datetime import datetime +from array import array + import redis -from array import * - -#-------------------------------------------------------------- -# COSTANTI -MSGLEN = 9 -TIMEOUTSERIALE = 10 -MAXRETRY = 10 - -# numero campioni filtraggio segnale ballerino -MAX_COUNTER_BLINK = 10 - -PROGRAM_NAME ="ReadPar IOB-pi v.2.6.2" - -# DA FILE CONF -idxMacchina = "1001" -SAMPLETIME = 0.1 -TIMEOUTSHORT = (SAMPLETIME*20) -TIMEOUTLONG = (SAMPLETIME*600) -SENDURLTIME = 0.08 -NMAXSEND = 5 # numero massimo di invii per singolo ciclo di svuotamento - -# VAR -to_enable = False -to_short = TIMEOUTSHORT -to_long = TIMEOUTLONG -to_serial = TIMEOUTSERIALE -to_retry = MAXRETRY -errormsglen = 0 - -# VAR - -out_0 = 24 -out_1 = 26 - -in_0 = 11 -in_1 = 12 -in_2 = 13 -in_3 = 15 -in_4 = 16 -in_5 = 18 -in_6 = 22 -in_7 = 7 -in_8 = 29 -in_9 = 31 -in_10 = 32 -in_11 = 36 - -# contatore: serve x match tra accoda ed invia x possibile controllo a posteriori... ogni volta che accodo incremento di 1, va da 0 a 9999 -cont = '0' - -# variabile stato online/offline della macchina -onLine = '1' - -# variabile stato seinding/waiting x la parte invio URL -sending = '0' - -# variabile stato timer thread busy -timer_busy = False - -# -# array per ingressi filtrati -i_counters = array ( 'i',[0,0,0,0,0,0,0,0,0,0,0,0]) -B_blinking = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) -B_previous = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) -B_input = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) -B_output = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) - -B_inverting = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) - -B_filter = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) -B_filter_prev = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) -B_temp = array ( 'B',[0,0,0,0,0,0,0,0,0,0,0,0]) -i_filter_counters = array ( 'i',[0,0,0,0,0,0,0,0,0,0,0,0]) - -#-------------------------------------------------------------- -# Gestione coda (condivisa) su Redis x registrazione eventi ed invio URL -CodaR = redis.Redis(host='localhost', port=6379, db=0, password='24068Seriate') -queue_name = 'IOB' - -#----------------------------------- -# Gestione code REDIS (sost. coda in ram) - -# Function to add an item to the queue (enqueue) -def rqEnqueue(item): - CodaR.rpush(queue_name, item) - #logQue.info(f"Enqueued: {item}") - -# Function to remove an item from the queue (dequeue) -def rqDequeue(): - item = CodaR.lpop(queue_name) - if item: - #logQue.info(f"Dequeued: {item.decode('utf-8')}") - return item.decode('utf-8') - else: - logQue.info("Queue is empty, nothing to retrieve!") - -# Function to count queue actual lenght in Redis -def rqLen(): - cLen = CodaR.llen(queue_name) - if cLen: - #logQue.info(f"Queue len: {cLen}") - return cLen - else: - #logQue.info("Queue is empty") - return 0 - -#--------------------------------------------------------------- -# lettura parallela -# ritorna il byte letto pulito ( due char hex ) - -def readParallelaFiltrata(): - - global in_0 - global in_1 - global in_2 - global in_3 - global in_4 - global in_5 - global in_6 - global in_7 - - global in_8 - global in_9 - global in_10 - global in_11 - - - global GPIO - - current = '' - - - try: - - if GPIO.input(in_0): - B_input[0] = 0 - else: - B_input[0] = 1 - - if GPIO.input(in_1): - B_input[1] = 0 - else: - B_input[1] = 1 - - if GPIO.input(in_2): - B_input[2] = 0 - else: - B_input[2] = 1 - - if GPIO.input(in_3): - B_input[3] = 0 - else: - B_input[3] = 1 - - if GPIO.input(in_4): - B_input[4] = 0 - else: - B_input[4] = 1 - - if GPIO.input(in_5): - B_input[5] = 0 - else: - B_input[5] = 1 - - if GPIO.input(in_6): - B_input[6] = 0 - else: - B_input[6] = 1 - - if GPIO.input(in_7): - B_input[7] = 0 - else: - B_input[7] = 1 - - if GPIO.input(in_8): - B_input[8] = 0 - else: - B_input[8] = 1 - - if GPIO.input(in_9): - B_input[9] = 0 - else: - B_input[9] = 1 - - if GPIO.input(in_10): - B_input[10] = 0 - else: - B_input[10] = 1 - - if GPIO.input(in_11): - B_input[11] = 0 - else: - B_input[11] = 1 - - - - #ciclo per ogni segnale - for i in range(12) : - #print (i) - - # v2.1 gestione inversione bit ingresso - - if ( B_inverting[i] == 1 ) : - if ( B_input[i] == 0 ) : - B_input[i] = 1 - else : - B_input[i] = 0 - - # v2.2 gestione filtro segnali brevi - - if ( B_filter[i] == 1 ) : - - # fronte 0 -> 1 - if ( B_input[i] == 1 ) and ( B_filter_prev [i] == 0 ) : - if ( i_filter_counters[i] == 0 ) : - # vero fronte 0 -> 1 - i_filter_counters[i] = MAX_COUNTER_FILTER - B_temp[i] = 0 # tengo l' ingresso a 0 - #logPro.info("START spike 0->1 on bit " + str(i) ) - else : - # fine disturbo breve di uno stato 1 - i_filter_counters[i] = 0 - B_temp[i] = 1 # tengo l' ingresso a 1 - logPro.info("END spike 0->1 on bit " + str(i) ) - - # stabile 1 -> 1 - if ( B_input[i] == 1 ) and ( B_filter_prev [i] == 1 ) : - if ( i_filter_counters[i] == 0 ) : - # segnale stabile a 1 - B_temp[i] = 1 # tengo l' ingresso a 1 - else : - # poco dopo il fronte - i_filter_counters[i] = i_filter_counters[i] - 1 - B_temp[i] = 0 # tengo l' ingresso a 0 - - # fronte 1 -> 0 - if ( B_input[i] == 0 ) and ( B_filter_prev [i] == 1 ) : - if ( i_filter_counters[i] == 0 ) : - # vero fronte 1 -> 0 - i_filter_counters[i] = MAX_COUNTER_FILTER - B_temp[i] = 1 # tengo l' ingresso a 1 - #logPro.info("START spike 1->0 on bit " + str(i) ) - else : - # fine disturbo breve di uno stato 0 - i_filter_counters[i] = 0 - B_temp[i] = 0 # tengo l' ingresso a 0 - logPro.info("END spike 1->0 on bit " + str(i) ) - - # stabile 0 -> 0 - if ( B_input[i] == 0 ) and ( B_filter_prev [i] == 0 ) : - if ( i_filter_counters[i] == 0 ) : - # segnale stabile a 0 - B_temp[i] = 0 # tengo l' ingresso a 0 - else : - # poco dopo il fronte - i_filter_counters[i] = i_filter_counters[i] - 1 - B_temp[i] = 1 # tengo l' ingresso a 1 - - B_filter_prev [i] = B_input[i] - B_input[i] = B_temp[i] - - # fine gestione filtro segnali brevi - - - # se non blinking, copia ingresso - if ( B_blinking[i] == 0 ) : - B_output[i] = B_input[i] - else: - # gestione segnale blinking - # se fronte del segnale - if ( B_previous[i] != B_input[i] ) : - B_previous[i] = B_input[i] - # se fronte di salita - if ( B_input[i] == 1 ) : - # subito uscita = 1 - B_output[i] = 1 - i_counters[i] = MAX_COUNTER_BLINK - #else : - # # loggo che ho rilevato un blink... - # logPro.info("Blink down on bit " + str(i)) - else: - # no , segnale eguale a prima - # se input a 0 - if ( B_input[i] == 0 ) : - # E CONTEGGIO IN CORSO - if ( i_counters[i] > 0 ) : - i_counters[i] = i_counters[i] -1 - if ( i_counters[i] == 0 ) : - B_output[i] = 0 - logPro.info("END Blink on bit " + str(i) ) - - #Rimettiamo insieme i bit - new_value = 0 - - if ( B_output[0] == 1 ) : - new_value = new_value + 1 - if ( B_output[1] == 1 ) : - new_value = new_value + 2 - if ( B_output[2] == 1 ) : - new_value = new_value + 4 - if ( B_output[3] == 1 ) : - new_value = new_value + 8 - if ( B_output[4] == 1 ) : - new_value = new_value + 16 - if ( B_output[5] == 1 ) : - new_value = new_value + 32 - if ( B_output[6] == 1 ) : - new_value = new_value + 64 - if ( B_output[7] == 1 ) : - new_value = new_value + 128 - if ( B_output[8] == 1 ) : - new_value = new_value + 256 - if ( B_output[9] == 1 ) : - new_value = new_value + 512 - if ( B_output[10] == 1 ) : - new_value = new_value + 1024 - if ( B_output[11] == 1 ) : - new_value = new_value + 2048 - - current = hex( new_value ).replace ( "0x" , "" ).upper() - - except Exception as e: - print ("Errore in readParallelaFiltrata \n\n") - print (str(e)) - pass - - return current - -#--------------------------------------------------------------- -#Funzione di scrittura su coda con try-except - -def accoda(): - - try: - dtEve = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')[:-3] - rqEnqueue(dtEve + '#' + value + '#' + cont) - except Exception as e: - logPro.error( "QUEUE:Errore coda \n\n" ) - logPro.error(str(e)) - -#-------------------------------------------------------------- -# svuotaCoda x invio dati al server -def svuotaCoda(): - - global onLine - global sending - global timer_busy - global to_retry - global NMAXSEND - - #logPro.info ("start timer ") - if ( timer_busy == False ): - timer_busy = True - #logPro.info ("start timer ok ") - - try: - if not rqLen() == 0: - #logPro.info ("coda da svuotare!") - response = urllib.request.urlopen(URLALIVE) - answ = response.read().decode('utf-8') - #logPro.info(answ) - if answ == 'OK': - #logPro.info ("OK alive") - response2 = urllib.request.urlopen(URLENABLED + idxMacchina) - answ2 = response2.read().decode('utf-8') - if answ2 == 'OK': - # aggiorno stato ad online - if onLine == '0': - logPro.info("IOB ONLINE!") - - onLine = '1' # imposto comunque online - else: - if onLine == '1': - logPro.error("IOB offline") - - onLine = '0' - else: - if onLine == '1': - logPro.error("Server offline") - - onLine = '0' - - # ora verifico SE si possa inviare (ovvero sia online server e NON ci siano altri send attivi...) - if onLine == '1': - if sending == '0': - #segnalo che sono in sending! - sending = '1' - - # SAM 2016.12.23: modifica x invio FINO A nMaxSend ELEMENTI ad ogni ciclo di svuotamento - i = NMAXSEND - - while i >= 0: - if not rqLen() == 0: - - # formatto dataOra corrente - dtCurr = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')[:-3] - - #prendo primo elemento dalla coda - resp = rqDequeue() - - # recupero valori da elemento coda! - dtEve = resp.split("#")[0] - value = resp.split("#")[1] - cnt = resp.split("#")[2] - url = URLBASE + idxMacchina + URLADV1 + value - url = url + '&dtCurr=' + dtCurr + '&dtEve=' + dtEve + '&cnt=' + cnt - # CHIAMO URL - response3 = urllib.request.urlopen ( url ) - answ3 = response3.read().decode('utf-8') - #logSnd.info(url) - - # log valore inviato! - logSnd.info( value + ' ['+ cnt +']' + ' R:' + answ3 ) - #logSnd.info ("Valore smaltito dalla coda") - - # tolgo 1 al contatore - i -= 1 - - # completato invio, riporto sending a zero! - sending = '0' - else: - if to_retry > 0: - to_retry -= 1 - logPro.info("WAIT active send to complete") - else: - sending = '0' - to_retry = MAXRETRY - logPro.info("END WAIT, reset to_retry var") - - else: - pass - - else: - pass - - except Exception as e: - if onLine == '1': - logPro.error("Server Non raggiungibile") - logPro.error(str(e)) - - onLine = '0' - - finally: - # in ogni caso - timer_busy = False - - #print ("end timer ok") - - #print ("end timer ") - -#--------------------------------------------------------------- -# funzione timer thread -#--------------------------------------------------------------- -def do_every (interval, worker_func, iterations = 0): - if iterations != 1: - threading.Timer ( - interval, - do_every, [interval, worker_func, 0 if iterations == 0 else iterations-1] - ).start (); - - worker_func (); - -#--------------------------------------------------------------- -# gestione contatore -#--------------------------------------------------------------- -def contatore(): - - try: - global cont - ctr = int(cont) - ctr +=1 - ctr = ctr % 10000 # round robin 10000 eventi x track - cont = str(ctr) - except Exception as e: - print("errore incremento contatore \n\n") - print(str(e)) - -#--------------------------------------------------------------- -# avvia porta parallela -#--------------------------------------------------------------- -def avviaParallela(): - - global in_0 - global in_1 - global in_2 - global in_3 - global in_4 - global in_5 - global in_6 - global in_7 - global in_8 - global in_9 - global in_10 - global in_11 - - global GPIO - - try: - - GPIO.setmode(GPIO.BOARD) - - GPIO.setwarnings(False) - - #GPIO.setup(out_0, GPIO.OUT) # output 0 - #GPIO.setup(out_1, GPIO.OUT) # output 1 - - GPIO.setup(in_0, GPIO.IN) # input 0 - GPIO.setup(in_1, GPIO.IN) # input 1 - GPIO.setup(in_2, GPIO.IN) # input 2 - GPIO.setup(in_3, GPIO.IN) # input 3 - GPIO.setup(in_4, GPIO.IN) # input 4 - GPIO.setup(in_5, GPIO.IN) # input 5 - GPIO.setup(in_6, GPIO.IN) # input 6 - GPIO.setup(in_7, GPIO.IN) # input 7 - GPIO.setup(in_8, GPIO.IN) # input 8 - GPIO.setup(in_9, GPIO.IN) # input 9 - GPIO.setup(in_10, GPIO.IN) # input 10 - GPIO.setup(in_11, GPIO.IN) # input 11 - - except Exception as e: - print( "\n\n" + PROGRAM_NAME + " - Error 3 on RPi.GPIO ! \n\n") - print (str(e)) - sys.exit(1) - - - print( "\n\n" + PROGRAM_NAME + " - init ok \n\n") - - -#--------------------------------------------------------------- -#--------------------------------------------------------------- -# MAIN - -try: - config = configparser.RawConfigParser() - config.read ( 'IOB.cfg' ) - - SAMPLETIME = config.getfloat ( 'time' , 'SAMPLETIME' ) - TIMEOUTSHORT = config.getfloat ( 'time' , 'TIMEOUTSHORT' ) - TIMEOUTLONG = config.getfloat ( 'time' , 'TIMEOUTLONG' ) - SENDURLTIME = config.getfloat ( 'time' , 'SENDURLTIME' ) - NMAXSEND = config.getint ( 'time' , 'NMAXSEND' ) - - idxMacchina = config.get ( 'id' , 'idxMacchina' ) - - - URLBASE = config.get ( 'web' , 'URLBASE' ) - URLENABLED = config.get('web' , 'URLENABLED') - URLALIVE = config.get ('web' , 'URLALIVE') - URLADV1 = config.get ( 'web' , 'URLADV1' ) - - LOGFILE = config.get ( 'log' , 'LOGFILE' ) - LOGLEVEL = config.get ( 'log' , 'LOGLEVEL' ) - - B_blinking[0] = config.getint ( 'blink' , 'bit0' ) - B_blinking[1] = config.getint ( 'blink' , 'bit1' ) - B_blinking[2] = config.getint ( 'blink' , 'bit2' ) - B_blinking[3] = config.getint ( 'blink' , 'bit3' ) - B_blinking[4] = config.getint ( 'blink' , 'bit4' ) - B_blinking[5] = config.getint ( 'blink' , 'bit5' ) - B_blinking[6] = config.getint ( 'blink' , 'bit6' ) - B_blinking[7] = config.getint ( 'blink' , 'bit7' ) - B_blinking[8] = config.getint ( 'blink' , 'bit8' ) - B_blinking[9] = config.getint ( 'blink' , 'bit9' ) - B_blinking[10] = config.getint ( 'blink' , 'bit10' ) - B_blinking[11] = config.getint ( 'blink' , 'bit11' ) - - MAX_COUNTER_BLINK = config.getint ( 'blink' , 'MAX_COUNTER_BLINK' ) - - # cv 2.1 se bit = 1 allora inverto segnale in ingresso... - B_inverting[0] = config.getint ( 'invert' , 'bit0' ) - B_inverting[1] = config.getint ( 'invert' , 'bit1' ) - B_inverting[2] = config.getint ( 'invert' , 'bit2' ) - B_inverting[3] = config.getint ( 'invert' , 'bit3' ) - B_inverting[4] = config.getint ( 'invert' , 'bit4' ) - B_inverting[5] = config.getint ( 'invert' , 'bit5' ) - B_inverting[6] = config.getint ( 'invert' , 'bit6' ) - B_inverting[7] = config.getint ( 'invert' , 'bit7' ) - B_inverting[8] = config.getint ( 'invert' , 'bit8' ) - B_inverting[9] = config.getint ( 'invert' , 'bit9' ) - B_inverting[10] = config.getint ( 'invert' , 'bit10' ) - B_inverting[11] = config.getint ( 'invert' , 'bit11' ) - - # cv 2.2 se bit = 1 allora filtro segnali brevi ... - B_filter[0] = config.getint ( 'filter' , 'bit0' ) - B_filter[1] = config.getint ( 'filter' , 'bit1' ) - B_filter[2] = config.getint ( 'filter' , 'bit2' ) - B_filter[3] = config.getint ( 'filter' , 'bit3' ) - B_filter[4] = config.getint ( 'filter' , 'bit4' ) - B_filter[5] = config.getint ( 'filter' , 'bit5' ) - B_filter[6] = config.getint ( 'filter' , 'bit6' ) - B_filter[7] = config.getint ( 'filter' , 'bit7' ) - B_filter[8] = config.getint ( 'filter' , 'bit8' ) - B_filter[9] = config.getint ( 'filter' , 'bit9' ) - B_filter[10] = config.getint ( 'filter' , 'bit10' ) - B_filter[11] = config.getint ( 'filter' , 'bit11' ) - - MAX_COUNTER_FILTER = config.getint ( 'filter' , 'MAX_COUNTER_FILTER' ) - - -except Exception as e: - print ("\n\n" + PROGRAM_NAME + ' - Error 4 - in config file ' 'IOB.cfg') - print (str(e)) - sys.exit(1) - -#-------------------------------------------- -# oggetto Logger -#-------------------------------------------- -try: - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(name)-8s %(levelname)-8s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - filename=LOGFILE, - filemode='a' - ) - - # aggiungo 2 logger specifici x queue e send... - logQue = logging.getLogger('queue') - logSnd = logging.getLogger('sendUrl') - logPro = logging.getLogger('program') - -except Exception as e: - # manda mail o simili - FARE!!! - print ("LOG: Impossibile creare file log con nome") - print (LOGFILE) - print ("\n\n") - print (str(e)) -#-------------------------------------------- - - -print ("\n\n" + PROGRAM_NAME + "\n\n") - -global startstatus -startstatus = 1 - -if startstatus == 1: - logPro.info("Avvio Programma" + PROGRAM_NAME) - -## Verifica l'OS e di conseguenza carica il file relativo con metodo di lockfile appropriato + check singola istanza -if os.name == 'posix': - import unix -else: - import win - -logPro.info( "Start " + PROGRAM_NAME ) - - -# lettura file configurazione -print ( ' idxMacchina = %s' % ( idxMacchina ) ) -print ( ' SAMPLETIME = %4.2f' % ( SAMPLETIME ) ) -print ( ' TIMEOUTSHORT = %4.2f' % ( TIMEOUTSHORT ) ) -print ( ' TIMEOUTLONG = %4.2f' % ( TIMEOUTLONG ) ) -print ( ' SENDURLTIME = %4.2f' % ( SENDURLTIME ) ) -print ( ' URLBASE = %s' % ( URLBASE ) ) -print ( ' URLADV1 = %s' % ( URLADV1 ) ) -print ( ' LOGFILE = %s' % ( LOGFILE ) ) -print ( ' LOGLEVEL = %s' % ( LOGLEVEL ) ) - -# -sys.stdout.write ( 'idxMacchina ?' + idxMacchina + '\n') - -to_short = TIMEOUTSHORT -to_long = TIMEOUTLONG - - -#-------------------------------------------------------------- -# apertura parallela +import requests +# 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 RuntimeError: - print( "\n\n" + PROGRAM_NAME + " - Error 1 - you need superuser privileges") -except Exception as e: - print( "\n\n" + PROGRAM_NAME + " - Error 2 - you need superuser privileges. USE 'sudo' to run your script\n\n") - print (str(e)) - sys.exit(1) +except ImportError: + GPIO = None +class ReadParallelaIOB: + def __init__(self, config_path='IOB.cfg'): + self.PROGRAM_NAME = "ReadPar IOB-pi v.3.0.0 (Refactored)" + self.MAXRETRY = 10 + self.MAX_COUNTER_BLINK = 10 + + # Configuration and State + self.config = configparser + self.load_config(config_path) + + # 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) -avviaParallela() + # 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() -#-------------------------------------------------------------- -# Qui avvio thread periodico di "svuotaCoda" + def load_config(self, path): + config = configparser.RawConfigParser() + if not os.path.exists(path): + print(f"Error: Config file {path} not found.") + sys.exit(1) + config.read(path) -#print ("Avvia svuota coda") -do_every ( SENDURLTIME , svuotaCoda ); + 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') -#--------------------------------------------------------------- -# ciclo forever and ever + self.URLBASE = config.get('web', 'URLBASE') + self.URLENABLED = config.get('web', 'URLENABLED') + self.URLALIVE = config.get('web', 'URLALIVE') + self.URLADV1 = config.get('web', 'URLADV1') -old = '' + self.LOGFILE = config.get('log', 'LOGFILE') + self.LOGLEVEL = config.get('log', 'LOGLEVEL') -#print ("Avvio ciclo") -logPro.info("Avvio loop principale") -while 1: + # 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') - try: - time.sleep (SAMPLETIME) - except Exception as e: - logPro.info("First_SLEEP: errore attesa sampletime") - logPro.error(str(e)) + def setup_logging(self): + 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') - # lettura dati da IOB - value = readParallelaFiltrata() - - if ( value != '' ) : - if value != old : - #loggo e invio dati - try: - logQue.info( value + ' ['+ cont +']') - errormsglen = 0 - accoda() - contatore() - except Exception as e: - logPro.error("URLBROWSER: errore registrazione valore e accoda") - logPro.error(str(e)) - pass - #enable e reset timer - to_enable = True - to_short = TIMEOUTSHORT - to_long = TIMEOUTLONG - - old = value - - - # gestione timeout breve - if ( to_enable ) : - to_short = to_short - SAMPLETIME - if to_short <= 0: - #loggo e invio dati - try: - logQue.info( '>' + value + ' ['+ cont +']') - errormsglen = 0 - accoda() - contatore() - except Exception as e: - logPro.error("URLBROWSER: errore registrazione valore e accoda TO_short") - logPro.error(str(e)) - pass - to_short = TIMEOUTSHORT - to_enable = False # dopo un colpo il timer breve viene disabilitato - to_long = TIMEOUTLONG - - # gestione timeout lungo - to_long = to_long - SAMPLETIME - if to_long <= 0: - #loggo e invio dati + def setup_gpio(self): + if GPIO is None: + self.logPro.error("GPIO library not found. Are you on a Raspberry Pi?") + sys.exit(1) try: - logQue.info( '>>' + value + ' ['+ cont +']') - errormsglen = 0 - accoda() - contatore() + 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: - logPro.error("URLBROWSER: errore registrazione valore e accoda TO_long") - logPro.error(str(e)) - pass - to_long = TIMEOUTLONG + self.logPro.error(f"GPIO Setup Error: {e}") + sys.exit(1) + + def rqEnqueue(self, item): + self.CodaR.rpush(self.queue_name, item) + + def rqDequeue(self): + item = self.CodaR.lpop(self.queue_name) + return item.decode('utf-8') if item else None + + def rqLen(self): + return self.CodaR.llen(self.queue_name) + + def readParallelaFiltrata(self): + try: + # 1. Efficient GPIO Read + for i, pin in enumerate(self.input_pins): + # Read and invert logic immediately if required + raw_val = 0 if GPIO.input(pin) else 1 + if self.B_inverting[i] == 1: + raw_val = 1 - raw_val + self.B_input[i] = raw_val + + # 2. Processing Loop (Filtering & Blinking) + for i in range(12): + # Debounce / Filter logic + if self.B_filter[i] == 1: + if self.B_input[i] == 1 and self.B_filter_prev[i] == 0: + if self.i_filter_counters[i] == 0: + self.i_filter_counters[i] = self.MAX_COUNTER_FILTER + self.B_temp[i] = 0 + else: + self.i_filter_counters[i] = 0 + self.B_temp[i] = 1 + elif self.B_input[i] == 1 and self.B_filter_prev[i] == 1: + if self.i_filter_counters[i] > 0: + self.i_filter_counters[i] -= 1 + self.B_temp[i] = 0 + else: + self.B_temp[i] = 1 + elif self.B_input[i] == 0 and self.B_filter_prev[i] == 1: + if self.i_filter_counters[i] == 0: + self.i_filter_counters[i] = self.MAX_COUNTER_FILTER + self.B_temp[i] = 1 + else: + self.i_filter_counters[i] = 0 + self.B_temp[i] = 0 + elif self.B_input[i] == 0 and self.B_filter_prev[i] == 0: + if self.i_filter_counters[i] > 0: + self.i_filter_counters[i] -= 1 + self.B_temp[i] = 1 + else: + self.B_temp[i] = 0 + + self.B_filter_prev[i] = self.B_input[i] + self.B_input[i] = self.B_temp[i] + + # Blinking Logic + if self.B_blinking[i] == 0: + self.B_output[i] = self.B_input[i] + else: + if self.B_previous[i] != self.B_input[i]: + self.B_previous[i] = self.B_input[i] + if self.B_input[i] == 1: + self.B_output[i] = 1 + self.i_counters[i] = self.MAX_COUNTER_BLINK + else: + if self.B_input[i] == 0 and self.i_counters[i] > 0: + self.i_counters[i] -= 1 + if self.i_counters[i] == 0: + self.B_output[i] = 0 + + # 3. Optimized Bitwise Reconstruction + new_value = 0 + for i in range(12): + if self.B_output[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.utcnow().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.utcnow().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): + 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("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: + break + except Exception as e: + self.logPro.error(f"Main loop error: {e}") + +if __name__ == "__main__": + app = ReadParallelaIOB() + app.run()