#!/usr/bin/python # -*- coding: utf-8 -*- # readSeriale v. 2.3e # - single instance timer # - invio multiplo x send eventi accodati # - attenzione .. non c'e' la possibilita di definire bit blinking, inversione ecc. # - gestione wait se troppi errori in msglen... #--------------------------------------------------------------- import serial import time import sys from datetime import datetime import urllib import ConfigParser import os, sys import logging import logging.handlers import threading import Queue from array import * #-------------------------------------------------------------- # COSTANTI MSGLEN = 5 TIMEOUTSERIALE = 10 MAXRETRY = 3 # numero campioni filtraggio segnale ballerino MAX_COUNTER_BLINK = 10 PROGRAM_NAME ="ReadSer IOB-w v.2.3e" # DA FILE CONF idxMacchina = "L003" 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 errormsglen = 0 # 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]) B_blinking = array ( 'B',[0,0,0,0,0,0,0,0]) B_previous = array ( 'B',[0,0,0,0,0,0,0,0]) B_input = array ( 'B',[0,0,0,0,0,0,0,0]) B_output = array ( 'B',[0,0,0,0,0,0,0,0]) B_inverting = array ( 'B',[0,0,0,0,0,0,0,0]) B_filter = array ( 'B',[0,0,0,0,0,0,0,0]) B_filter_prev = array ( 'B',[0,0,0,0,0,0,0,0]) B_temp = array ( 'B',[0,0,0,0,0,0,0,0]) i_filter_counters = array ( 'i',[0,0,0,0,0,0,0,0]) #-------------------------------------------------------------- # Gestione coda (condivisa) x registrazione eventi ed invio URL #print "Creazione coda illimitata" Coda = Queue.Queue(0) #queueLock = threading.Lock() #--------------------------------------------------------------- #--------------------------------------------------------------- def ChiudiSerialeEsci(): global ser if ser.isOpen(): ser.close() sys.exit(1) #--------------------------------------------------------------- #--------------------------------------------------------------- # lettura buffer seriale e pulizia caratteri non stampabili # ritorna '00' se non c'è un messaggio buono o il messaggio pulito ( due bytes hex ) # il messaggio ha il formato xxi00 00xxx CON IL COMANDO $i # il messaggio ha il formato xxI00xxx CON IL COMANDO $I def readSeriale(): global to_serial global to_retry global errormsglen global ser ret = '' current = '80' i = 0 n_trials = 0 errormsglen = 0 b_ok = False try: while ( not b_ok ): if ser.inWaiting() < MSGLEN : # se non ci sono abbastanza caratteri time.sleep (.2) # attesa breve n_trials = n_trials + 1 if ( n_trials > 5 ) : logPro.error("err. 15 - msglen < 5 char - Trial # " + str(errormsglen) + " | " + str(ser.inWaiting())) RiavviaSeriale() time.sleep(.5) requestData() n_trials = 0 b_ok = True # esco dal loop #endif n_trials else : # finchè c'è robba .. leggi e tieni i buoni while ser.inWaiting() > 0 : try: c = ser.read(1) except Exception, e: logPro.error("err. 13 - error on try ser.read -- EXIT -- " + str(e) ) ChiudiSerialeEsci() #end try # filtra caratteri non stampabili if c > ' ' : current += c # sys.stdout.write(current + '<<<<\n') #end while # ora il messaggio ha il formato xxxxxi00 00xxx : cerco la 'i' iniziale # ora il messaggio ha il formato xxxxxI00xxx : cerco la 'I' iniziale try: while i < len(current) and current[i] != 'I': i = i + 1 #end while except: logPro.error("err. 14 - error on find I >>" + current +"<<") #end try # se non ho trovato la 'i' restituisco '00' if i == len(current)-1: return ret else: current = current[i+1:i+3] # levo i due bit piu alti ..... current = "{0:0>2X}".format(int ( current, 16 ) & 0X3F) #end if # richiesta dati ad IOB requestData() n_trials = 0 errormsglen = 0 b_ok = True # esco dal loop # sys.stdout.write ( current + '\n') #endif #end while not b_ok except Exception, e: logPro.error (' err. 18 - Serial Port error... --EXIT -- ---> ' + str(e) ) ChiudiSerialeEsci() return current #--------------------------------------------------------------- # richiesta dati ad IOB : scrittura su seriale def requestData (): global ser try : #$I per vers 1, $i per vers 2 scheda, prima con 6 seconda con 9 char risposta ser.write ("$I" + '\r\n') ser.flush() except : logPro.error ( "SERIAL: err. 19 - Write / flush") #--------------------------------------------------------------- #Funzione di scrittura su coda con try-except def accoda(): try: dtEve = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')[:-3] Coda.put(dtEve + '#' + value + '#' + cont) except Queue.Full: logPro.error( "Queue full" + `dtEve` + '#' + `value` + '#' + `cont` ) except: logPro.error( "NETWORK:Errore http-no com rete-timeout" + url ) # print "Url aforte" , url #-------------------------------------------------------------- # svuotaCoda x invio dati al server def svuota_coda(): global onLine global sending global timer_busy global NMAXSEND #print "start timer " if ( timer_busy == False ): timer_busy = True #print "start timer ok " try: if not Coda.empty(): #print "coda da svuotare!" response = urllib.urlopen(URLALIVE) answ = response.read() if answ == 'OK': #print "OK alive" response2 = urllib.urlopen(URLENABLED + idxMacchina) answ2 = response2.read() if answ2 == 'OK': # aggiorno stato ad online if onLine == '0': logPro.info("IOB ONLINE!") #print("IOB ONLINE") onLine = '1' # imposto comunque online else: if onLine == '1': logPro.error("IOB offline") #print("IOB offline") onLine = '0' else: if onLine == '1': logPro.error("Server offline") #print("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 Coda.empty(): # formatto dataOra corrente dtCurr = datetime.utcnow().strftime('%Y%m%d%H%M%S%f')[:-3] #prendo primo elemento dalla coda resp = Coda.get() # 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.urlopen ( url ) answ3 = response3.read() print(url) # log valore inviato! logSnd.info( value + ' ['+ cnt +']' + ' R:' + answ3 ) #print "Valore smaltito dalla coda" # tolgo 1 al contatore i -= 1 # completato invio, riporto sending a zero! sending = '0' else: logPro.info("WAIT active send to complete") else: pass else: pass except: if onLine == '1': logPro.error("Server Non raggiungibile") #print "Non raggiungibile" onLine = '0' # 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: print("errore incremento contatore") #--------------------------------------------------------------- # avvia porta seriale #--------------------------------------------------------------- def avviaSeriale(): global ser try: ser = serial.Serial( port = comm_port , baudrate = 9600 , parity = serial.PARITY_NONE , stopbits = serial.STOPBITS_ONE , bytesize = serial.EIGHTBITS, timeout = 5 ) except serial.serialutil.SerialException , e : sys.stdout.write ( '\n' + PROGRAM_NAME + ' err 11 - opening serial\n\n%s\n\n' % e ) ChiudiSerialeEsci() #end try print( "\n\n" + PROGRAM_NAME + " - init ok \n\n") #--------------------------------------------------------------- # Ri avvia porta seriale #--------------------------------------------------------------- def RiavviaSeriale(): global ser try: if ser.isOpen(): ser.close() ser = serial.Serial( port = comm_port , baudrate = 9600 , parity = serial.PARITY_NONE , stopbits = serial.STOPBITS_ONE , bytesize = serial.EIGHTBITS, timeout = 5 ) except serial.serialutil.SerialException , e : sys.stdout.write ( '\n' + PROGRAM_NAME + ' err 12 - reopening serial\n\n%s\n\n' % e ) ChiudiSerialeEsci() #end try #--------------------------------------------------------------- #--------------------------------------------------------------- #--------------------------------------------------------------- # 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' ) comm_port = config.get ( 'comm' , 'port' ) 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' ) 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' ) # 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' ) MAX_COUNTER_FILTER = config.getint ( 'filter' , 'MAX_COUNTER_FILTER' ) except: print "\n\n" + PROGRAM_NAME + ' - Error 4 - in config file ' + 'IOB.cfg' + " -- EXIT --" 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: # manda mail o simili - FARE!!! print "LOG: Impossibile creare file log con nome " print (LOGFILE) #-------------------------------------------- print "\n\n" + PROGRAM_NAME + "\n\n" ## 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 # [id] # idxMacchina = 2001 # [time] # SAMPLETIME = 0.1 # TIMEOUTSHORT = 200 # TIMEOUTLONG = 6000 print ( ' comm_port = %s' % ( comm_port ) ) 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 seriale avviaSeriale() #-------------------------------------------------------------- # MARCO: qui inserire avvio thread di "svuotaCoda" # avviaSvuotaCoda #print "Avvia svuota coda" do_every ( SENDURLTIME , svuota_coda ); #--------------------------------------------------------------- # ciclo forever and ever old = '' # richiesta dati ad IOB try: requestData() except: logPro.error("err 20 - try requestData -- EXIT --") ChiudiSerialeEsci() #print "Avvio ciclo" logPro.info("Avvio loop principale") while True : try: time.sleep (SAMPLETIME) except: logPro.error("err 21 - First_SLEEP: errore attesa sampletime") # lettura dati da IOB try: value = readSeriale() except: logPro.error("err 22 - main loop read serial -- EXIT --") ChiudiSerialeEsci() if ( value != '' ) : if value != old : #loggo e invio dati try: logQue.info( value + ' ['+ cont +']') accoda() contatore() except: logPro.error("err 23 - URLBROWSER: errore registrazione valore e accoda") 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 +']') accoda() contatore() except: logPro.error("err 24 - URLBROWSER: errore registrazione valore e accoda TO_short") 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 try: logQue.info( '>>' + value + ' ['+ cont +']') accoda() contatore() except: logPro.error("err 25 - URLBROWSER: errore registrazione valore e accoda TO_long") pass to_long = TIMEOUTLONG