Files
Mapo-IOB/IOB-PI/readParallela.py
T
2026-05-12 15:48:45 +02:00

324 lines
12 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import sys
import os
import logging
import logging.handlers
import threading
import configparser
from datetime import datetime
from array import array
import redis
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 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)
# 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):
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):
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):
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):
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()