diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..25e61872026a030adae5376d53637fa346be058b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+NAME = calc-stats-cli
+MAIN = CalcStats.py
+
+DIRNAME = $(NAME:-cli=)
+MODNAME = $(MAIN:.py=)
+PY_FILES += $(wildcard src/*.py)
+
+default: bin ${PY_FILES}
+	@cp ${PY_FILES} bin/${DIRNAME}
+	@echo "#!/usr/bin/env python\nimport sys\nsys.path.append(sys.path[0]+'/${DIRNAME}')\nfrom ${MODNAME} import main\nif __name__ == '__main__':\n    main()\n" > bin/${NAME}
+	@chmod +x bin/${NAME} bin/${DIRNAME}/${MAIN}
+
+bin:
+	@test -d $@ || mkdir -p $@/${DIRNAME}
+
+clean:
+	@rm -fr bin/ src/*~
+
+.PHONY: clean
diff --git a/src/CalcStats.py b/src/CalcStats.py
new file mode 100755
index 0000000000000000000000000000000000000000..a645284f65631e630e29f6a77486dd6308136da0
--- /dev/null
+++ b/src/CalcStats.py
@@ -0,0 +1,1239 @@
+#!/usr/bin/python3
+
+# this script calculates the instantaneous, average and peak of the booster current
+# python
+
+# G. Gaio
+
+from tango import *
+#import matplotlib.pyplot as plt
+import numpy as np
+from scipy.stats import kurtosis, skew
+import time, datetime
+import sys, os
+import math
+import argparse
+
+SENSOR_TAG = "Sensor"
+TARGET_TAG = "Target"
+
+property_name_list = ['SensorConfiguration','TargetConfiguration']
+tango_states=["ON","OFF","CLOSE","OPEN","INSERT","EXTRACT","MOVING","STANDBY","FAULT","INIT","RUNNING","ALARM","DISABLE","UNKNOWN"]
+
+MAX_NUM_SENSORS = 120
+MAX_NUM_TARGETS = 20
+
+SENSOR_TAG = "Sensor"
+TARGET_TAG = "Target"
+
+ACQ_RUN = 0x1
+RECOV_RUN = 0x2
+STATE_ERR = 0x4
+ACQ_ERR = 0x8
+TANGO_ERR = 0x10
+SAMPLES_ERR = 0x20
+EMPTY_ERR = 0x40
+
+ALL_MASK = ACQ_RUN | RECOV_RUN | STATE_ERR | ACQ_ERR | TANGO_ERR | SAMPLES_ERR | EMPTY_ERR
+ERR_MASK = STATE_ERR | ACQ_ERR | TANGO_ERR | SAMPLES_ERR | EMPTY_ERR
+
+NAN_DBG = 0
+DATA_DBG = 0
+ERR_DBG = 0
+
+HISTORY_SIZE = 1000
+
+min_process_samples = 0
+process_samples = 0
+
+#
+# state2mask()
+# Convert list of state labels into a bitmask
+#
+def state2mask(state_string):
+	device_allowed_states = state_string.split(",")
+	if len(device_allowed_states) == 0:
+		device_allowed_states.append(state_string)
+	allowed_indexes = [tango_states.index(x) for x in device_allowed_states ]
+	state_mask = 0	
+	for x in allowed_indexes:
+		state_mask = state_mask + (1 << x)
+	return state_mask
+
+#
+# getOptions()
+# Parse properties
+#
+def getOptions(args):
+	parser = argparse.ArgumentParser(description="Parses command.")
+	parser.add_argument("-dev", "--device_name", help="Tango Device Name")
+	parser.add_argument("-dsc", "--device_desc", help="Device description")
+	parser.add_argument("-es", "--enable_states", help="Enable tango states [ex: ON,OFF,STANDBY]")
+	parser.add_argument("-et", "--enable_targets", type=int, help="Enable target correlation mask [ex: 0xaa]")
+	parser.add_argument("-min", "--min_thres_val", type=float, help="Minumum threshold value")
+	parser.add_argument("-max", "--max_thres_val", type=float, help="Maximum threshold value")
+	parser.add_argument("-ltp", "--low_thres_perc", type=float, help="Low percentile threshold [0..1]")
+	parser.add_argument("-htp", "--high_thres_perc",type=float, help="High percentile threshold [0..1]")
+	parser.add_argument("-cvme", "--coeff_var_mean_exp", type=float, help="Coefficient of Variation Mean Exponent [0..1]")
+	parser.add_argument("-cvse", "--coeff_var_std_exp",type=float, help="Coefficient of Variation STD Exponent [0..1]")
+	options = parser.parse_args(args)
+	return options
+
+
+
+#
+# getOptions()
+# Parse properties and set device default configuration
+#
+def setOptions(seq_devname,seq_property_devname):
+
+	seq_dev = DeviceProxy(seq_devname)
+	seq_property_dev = DeviceProxy(seq_property_devname)
+
+	for property_name in property_name_list:
+
+		# get a dictionary from the property SensorConfiguration/TargetConfiguration
+		device_conf_dict = seq_property_dev.get_property(property_name)  
+
+		#print('device',seq_property_dev,'property',property_name)
+
+		idx = 1
+		if property_name == 'SensorConfiguration':
+			name = 'Sensor'; prefix = 's'
+		else:
+			name = 'Target'; prefix = 't'		 
+
+		for device in device_conf_dict[property_name]:
+			device_conf = getOptions(device.split(" ")) # compress a space separate string into a array used by the parse
+			# device settings 
+			try:
+				attribute=prefix+'{:03d}_'.format(idx)+name+"Name"; seq_dev.write_attribute(attribute,device_conf.device_name)
+				attribute=prefix+'{:03d}_'.format(idx)+name+"Desc"; seq_dev.write_attribute(attribute,device_conf.device_desc)
+				attribute=prefix+'{:03d}_'.format(idx)+"EnableStates"; seq_dev.write_attribute(attribute,state2mask(device_conf.enable_states))
+				attribute=prefix+'{:03d}_'.format(idx)+"EnableTargets"; seq_dev.write_attribute(attribute,device_conf.enable_targets);
+				attribute=prefix+'{:03d}_'.format(idx)+"MinThresVal"; seq_dev.write_attribute(attribute,device_conf.min_thres_val)
+				attribute=prefix+'{:03d}_'.format(idx)+"MaxThresVal"; seq_dev.write_attribute(attribute,device_conf.max_thres_val)
+				attribute=prefix+'{:03d}_'.format(idx)+"LowThresPerc"; seq_dev.write_attribute(attribute,device_conf.low_thres_perc)
+				attribute=prefix+'{:03d}_'.format(idx)+"HighThresPerc"; seq_dev.write_attribute(attribute,device_conf.high_thres_perc)
+				attribute=prefix+'{:03d}_'.format(idx)+"CoeffVarMeanExp"; seq_dev.write_attribute(attribute,device_conf.coeff_var_mean_exp)
+				attribute=prefix+'{:03d}_'.format(idx)+"CoeffVarStdExp"; seq_dev.write_attribute(attribute,device_conf.coeff_var_std_exp)
+				# indipendent settings
+				attribute=prefix+'{:03d}_'.format(idx)+"Enable"; seq_dev.write_attribute(attribute,True)
+			except DevFailed as df:	
+				if ERR_DBG:
+					print('Error configuring ', seq_devname)
+					print(str(df))				
+
+			idx = idx + 1
+
+
+class DeviceObj:  
+	def __init__(self, device_name, command, index, tag): 
+		# init variables
+		self.device_name = device_name  
+		self.command = command #Sensordevice1...TargetDevice1
+		self.index = index
+		self.tag = tag
+		# these variables are read from the sequencer
+		self.enable = True
+		self.enable_targets = 0xffffffff
+		self.enable_states = 0xffff
+		try:
+			self.device = DeviceProxy(self.device_name)
+			self.device.set_timeout_millis(200)
+			self.util = tango.Util.instance()
+			print(util.get_host_name())
+		except DevFailed as df:
+			if ERR_DBG:
+				print('Error initializing ', self.device_name,'/',self.command) 
+				print(str(df))	
+		except:
+			if ERR_DBG:
+				print('Error (generic) initializing ', self.device_name,'/',self.command) 							
+									
+		self.min_thres_val = -1000
+		self.max_thres_val = 1000
+		self.min_process_samples = 1
+		self.low_thres_perc = 0.1 # 0% discards bottom values
+		self.high_thres_perc = 0.9
+		self.coeff_var_mean_exp = 1
+		self.coeff_var_std_exp = 0
+		self.sample_shift = 0
+		#
+		# these variables are calculated and set to the sequencer
+		#
+		# state bits
+		# bit 0: acquisition set
+		# bit 1: acquisition running
+		# bit 2: state error
+		# bit 3: acquisition error
+		# bit 4: recovery running 
+		#
+		self.acq_state = 0  
+		self.mean = 0
+		self.minmax = 0
+		self.median = 0 
+		self.min = 0
+		self.max = 0  
+		self.std = 0
+		self.skew = 0
+		self.kurt = 0		
+		self.tot_samples = 0
+		self.valid_samples = 0
+		self.corr_low = np.zeros(MAX_NUM_TARGETS)
+		self.corr_high = np.zeros(MAX_NUM_TARGETS)
+		self.corr = np.zeros(MAX_NUM_TARGETS)
+		self.old_lp_filter_corr = np.zeros(MAX_NUM_TARGETS)
+		self.lp_filter_corr = np.zeros(MAX_NUM_TARGETS)
+		self.lp_filter_corr_min_thres_val = np.zeros(MAX_NUM_TARGETS)
+		self.lp_filter_corr_max_thres_val = np.ones(MAX_NUM_TARGETS)
+		self.sensor_direction = np.zeros(MAX_NUM_TARGETS) # -1 better target coeff variation with low sensor values, \
+		# support variables
+		self.target_coefvar_low = np.zeros(MAX_NUM_TARGETS)
+		self.target_coefvar_high = np.zeros(MAX_NUM_TARGETS)
+		self.last_bn_start = 0
+		self.last_bn_end = 0
+		self.bn_start = 0
+		self.bn_end = 0	
+		self.bn_end = 0	
+		self.bn_error = 0	
+		self.acquisition_samples = 1	
+		# raw data acquired from the device
+		self.data = np.zeros(10)
+		self.sort_data = np.zeros(10)
+		self.sort_filt_data = np.zeros(10)
+		# temporary data
+		self.min_thres_idx = np.zeros(10)
+		self.max_thres_idx = np.zeros(10)
+		self.nan_idx = np.zeros(10)
+		self.zero_idx = np.zeros(10)
+		self.sort_data_idx = np.zeros(10)
+		self.min_val_tmp = -1000
+		self.max_val_tmp = 1000
+
+		#trigger_attr_polling(self, dev, name) → None
+		self.polling_configured = False
+
+	#
+	# DeviceObj::get_data()
+	#
+	def get_data(self, new_bn_start, new_bn_end):
+
+		if self.enable == False:
+			self.acq_state &= ~ACQ_RUN
+			if DATA_DBG:
+				print('Disabled ', self.device_name,'/',command)
+				return
+		else:
+			self.acq_state |= ACQ_RUN
+
+		# retune bunch number start and end 
+		if self.last_bn_end <= new_bn_start:
+			bn_start = new_bn_start
+			bn_end = new_bn_end
+		else:
+			bn_start = self.last_bn_end + 1
+			bn_end = new_bn_end
+
+		# read device state
+		try:
+			cur_state = self.device.read_attribute('State'); 
+			if (int(1 << cur_state.value) & self.enable_states) == 0:
+				self.acq_state |= STATE_ERR
+				if ERR_DBG:
+					print('State error ', self.device_name,'/',self.command)
+				return
+			else:
+				self.acq_state &= ~STATE_ERR
+				self.acq_state &= ~TANGO_ERR
+
+			# A=np.array([1,2, 3, 4, 5]) A[2:]=array([3, 4, 5]) B=np.array([6,7]) A=np.append(A[2:],B)
+			# sample_shift recovers the data shift in respect the bunch number
+			data_tmp = np.array(self.device.command_inout(self.command,[1, int(bn_start+self.sample_shift), int(bn_end+self.sample_shift)]))	
+
+			self.data = np.append(self.data[bn_end-bn_start+1:],data_tmp) # circular buffer
+
+			# GG plot
+			#if self.command == 'GetArrival':
+			#	plt.cla()
+			#	plt.plot(self.data)
+			#	plt.show()
+			#	plt.pause(0.0001)
+
+			if DATA_DBG:
+				print('Acquired',self.device_name,self.command)
+
+			self.bn_start = new_bn_start
+			self.bn_end = new_bn_end
+			self.bn_error = 0
+			self.acq_state &= ~ACQ_ERR
+			self.acq_state &= ~RECOV_RUN
+			self.last_bn_start = bn_start
+			self.last_bn_end = bn_end
+
+		except DevFailed as df:	
+			self.bn_error = new_bn_end
+			self.acq_state |= ACQ_ERR
+			if ERR_DBG:
+				print('Error reading ', self.device_name,'/', self.command, ' bn=', int(bn_start), ' bnend=',int(bn_end))
+				print(str(df))
+
+		# recovery data strategy, ask data once at a time, accept at maximum 5 errors
+		# the skip data recovery
+		data_tmp = np.array([])
+
+		if self.bn_error == new_bn_end:
+			
+			self.acq_state |= RECOV_RUN
+
+			# get the last element of the array as the last good value
+			# the last valid value is used to fill the acquisition missed data
+			if len(self.data) > 0:
+				last_valid_val = self.data[-1] 
+			else:
+				last_valid_val = 0						
+
+			tango_error = 0
+			consec_error = 0
+			error_flag = False
+
+			for i in range(bn_start,bn_end):
+
+				try:
+					# in recovery mode get elements one by one
+					tmp = np.array(self.device.command_inout(self.command,[1, long(i+self.sample_shift), long(i+self.sample_shift)])) 
+					last_valid_val = tmp
+					consec_error = 0
+				except:
+					if ERR_DBG:
+						print('Max recovery error ', self.device_name,'/',self.command)
+					tango_error = tango_error + 1
+					consec_error = consec_error + 1
+					tmp = last_valid_val
+				
+				# if more then 10 % of errors, stop recovery
+				if tango_error > (bn_end - bn_start + 1) * 0.1:
+					error_flag = True
+					self.acq_state |= TANGO_ERR
+					self.acq_state &= ~ACQ_ERR
+					break
+
+				# if more then two consecutive error, stop recovery
+				if consec_error > 2:
+					error_flag = True
+					self.acq_state |= ACQ_ERR
+					self.acq_state &= ~TANGO_ERR
+					break
+
+				data_tmp = np.append(data_tmp, tmp)
+	
+			if error_flag == False:
+				# data recovered, append data to the object
+				self.data = np.append(self.data[bn_end-bn_start+1:],data_tmp) # circular buffer
+				self.bn_start = new_bn_start
+				self.bn_end = new_bn_end
+				# reset bn error
+				self.bn_error = 0
+				self.acq_state &= ~TANGO_ERR
+				self.acq_state &= ~ACQ_ERR					
+
+
+
+
+	#
+	# DeviceObj::prefix()
+	#
+	def prefix(self):
+		if self.tag == SENSOR_TAG:
+      			return "s";
+		elif self.tag == TARGET_TAG:
+			return "t";
+		else:
+			return "";
+
+	#
+	# DeviceObj::get_configuration()
+	#
+	def get_configuration(self, buf_len, acquisition_len, val_samples_perc):
+		try:
+			attribute=self.prefix()+'{:03d}_Enable'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.enable = val.value
+			attribute=self.prefix()+'{:03d}_EnableStates'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.enable_states = val.value   
+			attribute=self.prefix()+'{:03d}_EnableTargets'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.enable_targets = val.value   
+			attribute=self.prefix()+'{:03d}_MinThresVal'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.min_thres_val = val.value
+			attribute=self.prefix()+'{:03d}_MaxThresVal'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.max_thres_val = val.value  
+			attribute=self.prefix()+'{:03d}_LowThresPerc'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.low_thres_perc = val.value
+			attribute=self.prefix()+'{:03d}_HighThresPerc'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.high_thres_perc = val.value
+			attribute=self.prefix()+'{:03d}_CoeffVarMeanExp'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.coeff_var_mean_exp = val.value
+			attribute=self.prefix()+'{:03d}_CoeffVarStdExp'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.coeff_var_std_exp = val.value 
+			attribute=self.prefix()+'{:03d}_SampleShift'.format(self.index+1);val = selfseq_dev.read_attribute(attribute)
+			self.sample_shift = val.value 
+
+			if self.tag == SENSOR_TAG:
+				for target in target_dev:
+					if target.enable == True:
+						if self.enable_targets & (1 << target.index):
+							attribute='s{:03d}_t{:03d}_CorrFiltAbs_MinThresVal'.format(self.index+1,target.index+1)
+							val = selfseq_dev.read_attribute(attribute)
+							self.lp_filter_corr_min_thres_val[target.index] = val.value
+							attribute='s{:03d}_t{:03d}_CorrFiltAbs_MaxThresVal'.format(self.index+1,target.index+1)
+							val = selfseq_dev.read_attribute(attribute)
+							self.lp_filter_corr_max_thres_val[target.index] = val.value
+			elif self.tag == TARGET_TAG:
+				for target in target_dev:
+					if target.enable == True:
+						if (self.enable_targets & (1 << target.index)) and (self.index < target.index):
+							attribute='t{:03d}_t{:03d}_CorrFiltAbs_MinThresVal'.format(self.index+1,target.index+1)
+							val = selfseq_dev.read_attribute(attribute)
+							self.lp_filter_corr_min_thres_val[target.index] = val.value
+							attribute='t{:03d}_t{:03d}_CorrFiltAbs_MaxThresVal'.format(self.index+1,target.index+1)
+							val = selfseq_dev.read_attribute(attribute)
+							self.lp_filter_corr_max_thres_val[target.index] = val.value
+
+
+
+			if buf_len != len(self.data):
+				self.data = np.zeros(buf_len)
+				self.sort_data = np.zeros(buf_len)
+				self.sort_filt_data = np.zeros(buf_len)
+				# temporary data
+				self.min_thres_idx = np.zeros(buf_len)
+				self.max_thres_idx = np.zeros(buf_len)
+				self.nan_idx = np.zeros(buf_len)
+				self.zero_idx = np.zeros(buf_len)
+				self.sort_data_idx = np.zeros(buf_len)
+
+			if acquisition_len > buf_len:
+				acquisition_len = buf_len
+			self.acquisition_samples = acquisition_len
+			self.min_process_samples = buf_len * val_samples_perc
+
+		except DevFailed as df:	
+			if ERR_DBG:
+				print('Configuration reading error ', self.device_name,'/',command)
+				print(str(df))
+		except:
+			if ERR_DBG:
+				print('Configuration reading error ', self.device_name,'/',command)
+
+
+	#
+	# DeviceObj::set_sequencer()
+	#
+	def set_sequencer(self):
+
+		try:
+
+			# copy statistics to the sequencer
+			attribute=self.prefix()+'{:03d}_BunchNumberStart'.format(self.index+1);selfseq_dev.write_attribute(attribute,self.bn_start)
+			attribute=self.prefix()+'{:03d}_AcqState'.format(self.index+1);selfseq_dev.write_attribute(attribute,self.acq_state);
+			attribute=self.prefix()+'{:03d}_Mean'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.mean);
+			attribute=self.prefix()+'{:03d}_MinMax'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.minmax);
+			attribute=self.prefix()+'{:03d}_Min'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.min);
+			attribute=self.prefix()+'{:03d}_Max'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.max);
+			attribute=self.prefix()+'{:03d}_Median'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.median);
+			attribute=self.prefix()+'{:03d}_Std'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.std);
+			attribute=self.prefix()+'{:03d}_Skew'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.skew);
+			attribute=self.prefix()+'{:03d}_Kurt'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.kurt);
+			attribute=self.prefix()+'{:03d}_ValidSamples'.format(self.index+1);val = selfseq_dev.write_attribute(attribute,self.valid_samples);
+
+			# History for Mean,Median,Std,Skew,Kurt,Min,Max,MinMax
+			# mean			
+			attribute = self.prefix()+'{:03d}_Mean_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.mean; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.mean); selfseq_dev.write_attribute(attribute,val)
+			# median											 				
+			attribute = self.prefix()+'{:03d}_Median_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.median; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.median); selfseq_dev.write_attribute(attribute,val)
+			# std				
+			attribute = self.prefix()+'{:03d}_Std_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.std; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.std); selfseq_dev.write_attribute(attribute,val)		
+			# skew						
+			attribute = self.prefix()+'{:03d}_Skew_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.skew; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.skew); selfseq_dev.write_attribute(attribute,val)	
+			# kurt							
+			attribute = self.prefix()+'{:03d}_Kurt_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.kurt; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.kurt); selfseq_dev.write_attribute(attribute,val)	
+			# min				
+			attribute = self.prefix()+'{:03d}_Min_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.min; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.min); selfseq_dev.write_attribute(attribute,val)	
+			# max								
+			attribute = self.prefix()+'{:03d}_Max_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = val = np.roll(val, 1);val[-1] = self.max; selfseq_dev.write_attribute(attribute,val)
+			else:
+				np.append(val,self.max); selfseq_dev.write_attribute(attribute,val)	
+			# minmax					
+			attribute = self.prefix()+'{:03d}_MinMax_History'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+			if val.size >= HISTORY_SIZE: 
+				val = np.roll(val, 1);val[-1] = self.minmax; selfseq_dev.write_attribute(attribute,val)
+			else:
+				val = np.append(val,self.minmax); selfseq_dev.write_attribute(attribute,val)	
+
+			if self.tag == SENSOR_TAG:
+				for target in target_dev:
+					if target.enable == True:
+						if self.enable_targets & (1 << target.index):
+							attribute='s{:03d}_t{:03d}_Corr'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.corr[target.index]);
+							attribute='s{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.lp_filter_corr[target.index]);	
+							attribute='s{:03d}_t{:03d}_CorrHigh'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.corr_high[target.index]);
+							attribute='s{:03d}_t{:03d}_CorrLow'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.corr_low[target.index]);
+							attribute='s{:03d}_t{:03d}_Direction'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.sensor_direction[target.index]);
+
+							# History for Corr, CorrFiltAbs, CorrLow, CorrHigh
+							# corr
+							attribute = self.prefix()+'s{:03d}_t{:03d}_Corr'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.corr[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.corr[target.index]); selfseq_dev.write_attribute(attribute,val)	
+							# corrfiltabs							
+							attribute = self.prefix()+'s{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.lp_filter_corr[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.lp_filter_corr[target.index]); selfseq_dev.write_attribute(attribute,val)	
+							# corrlow								
+							attribute = self.prefix()+'s{:03d}_t{:03d}_CorrLow'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.corr_low[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.corr_low[target.index]); selfseq_dev.write_attribute(attribute,val)
+							# corrhigh																	
+							attribute = self.prefix()+'s{:03d}_t{:03d}_CorrHigh'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.corr_high[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.corr_high[target.index]); selfseq_dev.write_attribute(attribute,val)								
+							
+
+			elif self.tag == TARGET_TAG:
+				for target in target_dev:
+					if target.enable == True:
+						if (self.enable_targets & (1 << target.index)) and (self.index < target.index):
+							attribute='t{:03d}_t{:03d}_Corr'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.corr[target.index])
+							attribute='t{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.lp_filter_corr[target.index])
+							attribute='t{:03d}_t{:03d}_CorrHigh'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.corr_high[target.index])
+							attribute='t{:03d}_t{:03d}_CorrLow'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.corr_low[target.index])
+							attribute='t{:03d}_t{:03d}_Direction'.format(self.index+1,target.index+1)
+							selfseq_dev.write_attribute(attribute,self.sensor_direction[target.index])
+
+							# History for Corr, CorrFiltAbs, CorrLow, CorrHigh
+							# corr
+							attribute = self.prefix()+'t{:03d}_t{:03d}_Corr'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.corr[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.corr[target.index]); selfseq_dev.write_attribute(attribute,val)	
+							# corrfiltabs							
+							attribute = self.prefix()+'t{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.lp_filter_corr[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.lp_filter_corr[target.index]); selfseq_dev.write_attribute(attribute,val)	
+							# corrlow								
+							attribute = self.prefix()+'t{:03d}_t{:03d}_CorrLow'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.corr_low[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.corr_low[target.index]); selfseq_dev.write_attribute(attribute,val)
+							# corrhigh																	
+							attribute = self.prefix()+'t{:03d}_t{:03d}_CorrHigh'.format(self.index+1); val = selfseq_dev.read_attribute(attribute); val = val.value
+							if val.size >= HISTORY_SIZE: 
+								val = np.roll(val, 1);val[-1] = self.corr_high[target.index]; selfseq_dev.write_attribute(attribute,val)
+							else:
+								val = np.append(val,self.corr_high[target.index]); selfseq_dev.write_attribute(attribute,val)							
+							
+
+			attribute=self.prefix()+'{:03d}_BunchNumberEnd'.format(self.index+1);selfseq_dev.write_attribute(attribute,self.bn_end)
+			
+		except DevFailed as df:	
+			if ERR_DBG:
+				print('Setting sequencer error ', self.device_name)
+				print(str(df))
+		except:
+			if ERR_DBG:
+				print('Setting sequencer error ', self.device_name)
+
+	#
+	# DeviceObj::set_polling_attribute()
+	#
+	def set_polling_state(self):
+
+		if (self.index >= 0):
+			if self.polling_configured == False:
+				self.polling_configured = True
+				self.device.set_timeout_millis(10000)
+				if selfseq_dev.EnablePolling == True:
+					print("enable polling: ", self.device_name);
+						
+					#acquisition_period = int(selfseq_dev.AcquisitionPeriod * 1000 * 10)
+					acquisition_period = int(0)
+
+					# copy statistics to the sequencer
+					if (self.enable == True):
+						try:
+							attribute=self.prefix()+'{:03d}_AcqState'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:						
+							attribute=self.prefix()+'{:03d}_Mean'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_MinMax'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_Min'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_Max'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_Median'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_Std'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_Skew'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_Kurt'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+						try:
+							attribute=self.prefix()+'{:03d}_ValidSamples'.format(self.index+1);selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+						except:
+							print('Error configuring',attribute)
+					if self.tag == SENSOR_TAG:
+						for target in target_dev:
+							if (target.enable == True) and (self.enable == True):
+								if self.enable_targets & (1 << target.index):
+									attribute='s{:03d}_t{:03d}_Corr'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)											
+									attribute='s{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+									attribute='s{:03d}_t{:03d}_CorrHigh'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+									attribute='s{:03d}_t{:03d}_CorrLow'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+									attribute='s{:03d}_t{:03d}_Direction'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+
+					elif self.tag == TARGET_TAG:
+						for target in target_dev:
+							if (target.enable == True) and (self.enable == True):
+								if self.enable_targets & (1 << target.index):
+									attribute='t{:03d}_t{:03d}_Corr'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+									attribute='t{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)	
+									attribute='t{:03d}_t{:03d}_CorrHigh'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+									attribute='t{:03d}_t{:03d}_CorrLow'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+									attribute='t{:03d}_t{:03d}_Direction'.format(self.index+1,target.index+1)
+									try:
+										selfseq_dev.poll_attribute(attribute,acquisition_period);print('Enable poll',attribute,acquisition_period);
+									except:
+										print('Error configuring',attribute)
+				if selfseq_dev.DisablePolling == True:
+
+					# copy statistics to the sequencer
+					attribute=self.prefix()+'{:03d}_AcqState'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Mean'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_MinMax'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Min'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Max'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Median'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Std'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Skew'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_Kurt'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+					attribute=self.prefix()+'{:03d}_ValidSamples'.format(self.index+1);selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+
+					if self.tag == SENSOR_TAG:
+						for target in target_dev:
+							attribute='s{:03d}_t{:03d}_Corr'.format(self.index+1,target.index+1)
+							selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+							attribute='s{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1,target.index+1)
+							selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);	
+							attribute='s{:03d}_t{:03d}_CorrHigh'.format(self.index+1,target.index+1)
+							selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+							attribute='s{:03d}_t{:03d}_CorrLow'.format(self.index+1,target.index+1)
+							selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+							attribute='s{:03d}_t{:03d}_Direction'.format(self.index+1,target.index+1)
+							selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+
+					elif self.tag == TARGET_TAG:
+						for target in target_dev:
+							if (self.index+1) < (target.index+1):
+								attribute='t{:03d}_t{:03d}_Corr'.format(self.index+1,target.index+1)
+								selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+								attribute='t{:03d}_t{:03d}_CorrFiltAbs'.format(self.index+1,target.index+1)
+								selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+								attribute='t{:03d}_t{:03d}_CorrHigh'.format(self.index+1,target.index+1)
+								selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+								attribute='t{:03d}_t{:03d}_CorrLow'.format(self.index+1,target.index+1)
+								selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+								attribute='t{:03d}_t{:03d}_Direction'.format(self.index+1,target.index+1)
+								selfseq_dev.stop_poll_attribute(attribute);print('Disable poll',attribute);
+			
+
+
+
+		self.device.set_timeout_millis(1000)
+
+	#
+	# DeviceObj::filter_data()
+	#
+	def filter_data(self):
+
+		try:
+
+			self.tot_samples = len(self.data)
+
+			if (self.tot_samples == 0):
+				self.acq_state |= TANGO_ERR
+
+			if (self.acq_state & (STATE_ERR | ACQ_ERR | TANGO_ERR | SAMPLES_ERR)) or ((self.acq_state & ACQ_RUN) == 0):
+				if ERR_DBG:
+					print('Skip filtering',self.device_name,self.command,', state',self.acq_state)
+				return
+
+			# sort the array and get the indexes of the sorted array
+			self.sort_data = np.sort(self.data)
+			self.sort_data_idx = np.argsort(self.data)
+
+			# calculate the min max 
+			idx_min = int(math.floor(self.low_thres_perc * self.tot_samples))
+			idx_max = int(math.floor(math.ceil(self.high_thres_perc * self.tot_samples)-1))
+
+			self.min_thres_val_tmp = self.sort_data[idx_min]
+			if self.min_thres_val_tmp < self.min_thres_val:
+				self.min_thres_val_tmp = self.min_thres_val
+
+			self.max_thres_val_tmp = self.sort_data[idx_max]
+			if self.max_thres_val_tmp > self.max_thres_val:
+				self.max_thres_val_tmp = self.max_thres_val
+
+			# get the indexes of the elements outside the limits
+			if idx_min >= 0:
+				# get the indexes of the elements below the minimum threshold
+				# np.nonzero return a tuple of two arrays:
+				# Ex: self.min_thres_idx=(array([0, 1]),),    
+				# [0] return the first array
+				self.min_thres_idx = np.nonzero(self.data < self.min_thres_val_tmp)[0]
+
+			if idx_max <= (self.tot_samples-1):
+				# get the indexes of the elements above the maximum threshold
+				self.max_thres_thres_idx = np.nonzero(self.data > self.max_thres_val_tmp)[0]
+
+			# remove NaN (np.nan)
+			self.nan_idx = np.argwhere(np.isnan(self.sort_data));		
+
+			# remove elements equal to 0
+			self.zero_idx = np.where(self.sort_data == 0)[0]	
+
+			removed_samples_idx = np.concatenate((self.min_thres_idx, self.max_thres_thres_idx, self.nan_idx), axis=None)
+			self.sort_data_idx = [i for i in self.sort_data_idx  if i not in removed_samples_idx]
+
+			# [A[index] for index in B] extract the elements A with the indexex present in B
+			# get the array of filtered data (used to calculate statistics)
+			self.sort_filt_data = [self.data[index] for index in self.sort_data_idx]
+
+			if len(self.sort_filt_data) <= self.min_process_samples:
+				self.acq_state |= EMPTY_ERR
+			else:
+				self.acq_state &= ~EMPTY_ERR
+
+		except:
+			if ERR_DBG:
+				print('ERROR: filter_data(),',self.device_name)
+			self.acq_state |= EMPTY_ERR
+			return
+
+	#
+	# DeviceObj::check_nan_inf_stats()
+	#
+	def check_nan_inf_stats(self,reset):
+		if np.isnan(self.minmax) or np.isinf(self.minmax) or reset == True: 
+			self.minmax = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' minmax INF NAN')
+		if np.isnan(self.mean) or np.isinf(self.mean) or reset == True:
+			self.mean = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' mean INF NAN')
+		if np.isnan(self.min) or np.isinf(self.min) or reset == True:
+			self.min = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' min INF NAN')
+		if np.isnan(self.max) or np.isinf(self.max) or reset == True:
+			self.max = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' max INF NAN')
+		if np.isnan(self.median) or np.isinf(self.median) or reset == True:
+			self.median = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' median INF NAN')
+		if np.isnan(self.std) or np.isinf(self.std) or reset == True: 
+			self.std = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' std INF NAN')
+		if np.isnan(self.skew) or np.isinf(self.skew) or reset == True:
+			self.skew = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' skew INF NAN')
+		if np.isnan(self.kurt) or np.isinf(self.kurt) or reset == True:
+			self.kurt = 0
+			if NAN_DBG:
+				print(self.device_name,'/',self.command, ' kurt INF NAN')
+
+	#
+	# DeviceObj::process_stats()
+	#
+	def process_stats(self):
+
+		try:
+
+			if (self.acq_state & ERR_MASK)  or (self.acq_state & ACQ_RUN) == 0:
+				self.check_nan_inf_stats(True)
+				return;
+
+			# get the stats
+			#[self.valid_samples,self.minmax,self.avg,self.std,self.skew,self.kurt] = stats.describe(self.sort_filt_data)
+			#self.median = self.sort_filt_data[int(len(self.valid_samplest) / 2) - 1];
+			self.valid_samples = len(self.sort_filt_data)
+
+			self.minmax = np.ptp(self.sort_filt_data)
+			self.mean = np.mean(self.sort_filt_data)
+			self.min = np.min(self.sort_filt_data)
+			self.max = np.max(self.sort_filt_data)
+			self.median = self.sort_filt_data[int(self.valid_samples / 2) - 1];
+			self.std = np.std(self.sort_filt_data)
+			self.skew = skew(self.sort_filt_data)
+			self.kurt = kurtosis(self.sort_filt_data)
+
+			if DATA_DBG:
+				print(self.device_name,'/',self.command, ' valid samples ', self.valid_samples)
+				print(self.device_name,'/',self.command, ' minmax ', self.minmax)
+				print(self.device_name,'/',self.command, ' mean ', self.mean)
+				print(self.device_name,'/',self.command, ' min ', self.min)
+				print(self.device_name,'/',self.command, ' max ', self.max) 
+				print(self.device_name,'/',self.command, ' median ', self.median) 
+				print(self.device_name,'/',self.command, ' std ', self.std)
+				print(self.device_name,'/',self.command, ' skew ', self.skew)
+				print(self.device_name,'/',self.command, ' kurt ', self.kurt)
+
+			self.check_nan_inf_stats(False)
+
+		except:
+			if ERR_DBG:
+				print('ERROR: process_stats(),',self.device_name)
+			
+			self.valid_samples = 0
+			self.minmax = 0
+			self.mean = 0
+			self.min = 0
+			self.max = 0
+			self.median = 0
+			self.std = 0
+			self.skew = 0
+			self.kurt = 0
+			self.acq_state = self.acq_state | ERR_MASK
+			self.check_nan_inf_stats(True)
+
+
+	#
+	# DeviceObj::check_nan_inf_corr()
+	#
+	def check_nan_inf_corr(self,reset):
+		for dev in target_dev:
+			if np.isnan(self.corr[dev.index]) or np.isinf(self.corr[dev.index]) or reset == True: 
+				self.corr[dev.index] = 0
+				if NAN_DBG:
+					print('Correlation ',self.device_name,'/',self.command, ' ',dev.device_name,'/',dev.command,' NAN INF')
+					
+			if np.isnan(self.lp_filter_corr[dev.index]) or np.isinf(self.lp_filter_corr[dev.index]) or reset == True: 
+				self.lp_filter_corr[dev.index] = 0
+				if NAN_DBG:
+					print('Low Pass Filter Correlation ',self.device_name,'/',self.command, ' ',dev.device_name,'/',dev.command,' NAN INF')
+					
+			if np.isnan(self.corr_low[dev.index]) or np.isinf(self.corr_low[dev.index]) or reset == True:
+				self.corr_low[dev.index] = 0
+				if NAN_DBG:
+					print('Correlation low ',self.device_name,'/',self.command, ' ',dev.device_name,'/',dev.command,' NAN INF')
+					
+			if np.isnan(self.corr_high[dev.index]) or np.isinf(self.corr_high[dev.index]) or reset == True:
+				self.corr_high[dev.index] = 0
+				if NAN_DBG:
+					print('Correlation high ',self.device_name,'/',self.command, ' ',dev.device_name,'/',dev.command,' NAN INF')
+					
+			if np.isnan(self.sensor_direction[dev.index]) or np.isinf(self.sensor_direction[dev.index]) or reset == True:
+				self.sensor_direction[dev.index] = 0
+				if NAN_DBG:
+					print('Sensor direction ',self.device_name,'/',self.command, ' ',dev.device_name,'/',dev.command,' NAN INF')
+
+	def process_corr(self):
+
+		if (self.acq_state & ERR_MASK) or (self.acq_state & ACQ_RUN) == 0:
+			self.check_nan_inf_corr(True)
+			return;
+
+		try:
+
+			for dev in target_dev:
+
+				t_idx = dev.index 
+
+				do_corr = True
+				if (self.tag == TARGET_TAG) and (dev.tag == TARGET_TAG) and (self.index >= dev.index):
+					do_corr = False
+
+				if (self.acq_state & ERR_MASK == 0) and  (dev.acq_state & ERR_MASK == 0) and ((self.enable_targets & (1 << dev.index)) > 0) and do_corr:
+
+
+					# array of indexes that are both valid for the sensor and the target 
+					valid_idx = (np.intersect1d(self.sort_data_idx, dev.sort_data_idx))
+
+					if len(valid_idx) > 0:
+
+						sensor_array = self.data[valid_idx]
+						target_array = dev.data[valid_idx]
+
+						# sort B and change the order of A according to B
+						# ZB, ZA = zip(*[(a, b) for a, b in sorted(zip(B, A))])
+						sensor_array_sort,target_array_sort = zip(*[(a, b) for a, b in sorted(zip(sensor_array, target_array))]) 
+
+						# zip sensor and target, the sort will be done in respect the sensor
+						#zipped = list(zip(sensor_array,target_array))
+
+						# sort the array of tuple
+						#zipped.sort()
+						# unzip the array of tuples into two separate arrays
+						#sensor_sort_array,target_sort_array = map(None, *zipped)
+						
+						# calculate the correlation, numpy indexes start from 1 
+						self.corr[t_idx] = np.corrcoef(sensor_array,target_array)[0,1]
+						# low pass filter correlation
+						self.old_lp_filter_corr[t_idx] = self.lp_filter_corr[t_idx];
+						self.lp_filter_corr[t_idx] = lp_filter * np.absolute(self.corr[t_idx]) + (1 - lp_filter) * self.old_lp_filter_corr[t_idx]
+
+						# correlation calculated with the bottom half of sensor values
+						self.corr_low[t_idx] = np.corrcoef(sensor_array_sort[1:int(self.valid_samples/2)],target_array_sort[1:int(self.valid_samples/2)])[0,1]
+						# correlation calculated with the top half of sensor values
+						self.corr_high[t_idx] = np.corrcoef(sensor_array_sort[int(self.valid_samples/2):self.valid_samples],target_array_sort[int(self.valid_samples/2):self.valid_samples])[0,1]
+
+						# coefficiet of variation calculated for the bottom half of the target values
+						AL = np.power(np.mean(target_array_sort[1:int(self.valid_samples/2)]),self.coeff_var_mean_exp) 
+						BL = np.power(np.std(target_array_sort[1:int(self.valid_samples/2)]),self.coeff_var_std_exp)
+
+						if BL != 0:
+							self.target_coefvar_low[t_idx] = AL / BL
+						else:
+							self.target_coefvar_low[t_idx] = 0
+
+						# coefficiet of variation calculated for the top half of the target values
+						AH = np.power(np.mean(target_array_sort[int(self.valid_samples/2):self.valid_samples]),self.coeff_var_mean_exp) 
+						BH = np.power(np.std(target_array_sort[int(self.valid_samples/2):self.valid_samples]),self.coeff_var_std_exp)
+						self.target_coefvar_high[t_idx] = AH / BH
+
+						if BH != 0:
+							self.target_coefvar_high[t_idx] = AH / BH
+						else:
+							self.target_coefvar_high[t_idx] = 0
+						
+						if self.target_coefvar_low[t_idx] > self.target_coefvar_high[t_idx]:
+							self.sensor_direction[t_idx] = -1
+						elif self.target_coefvar_low[t_idx] < self.target_coefvar_high[t_idx]:
+							self.sensor_direction[t_idx] = 1
+						else:
+							self.sensor_direction[t_idx] = 0
+
+						if DATA_DBG:
+							print('Corr ', self.device_name,'/',self.command,' ',dev.device_name,'/',dev.command,' ',self.corr[t_idx])
+							print('Corr lp', self.device_name,'/',self.command,' ',dev.device_name,'/',dev.command,' ',self.lp_filter_corr[t_idx])
+							print('Corr low ', self.device_name,'/',self.command,' ',dev.device_name,'/',dev.command,' ',self.corr_low[t_idx])
+							print('Corr high ', self.device_name,'/',self.command,' ',dev.device_name,'/',dev.command,' ',self.corr_high[t_idx])
+							print('Sensor direction ', self.device_name,'/',self.command,' ',dev.device_name,'/',dev.command,' ',self.sensor_direction[t_idx])
+
+					else:
+						self.corr[t_idx] = 0
+						self.lp_filter_corr[t_idx] = 0
+						self.corr_low[t_idx] = 0
+						self.corr_high[t_idx] = 0
+						self.sensor_direction[t_idx] = 0
+		
+				else:
+					self.corr[t_idx] = 0
+					self.lp_filter_corr[t_idx] = 0
+					self.corr_low[t_idx] = 0
+					self.corr_high[t_idx] = 0
+					self.sensor_direction[t_idx] = 0
+
+			self.check_nan_inf_corr(False)
+		
+		except:
+			self.acq_state = self.acq_state | ERR_MASK
+			self.check_nan_inf_corr(True)
+			return;
+
+#
+# get_devname_cmd()
+#
+def get_devname_cmd(label,idx): 
+	# sensorname: s001_SensorName
+	if label == 'Sensor':
+		attribute='s{:03d}_'.format(idx+1)+label+'Name'
+	else:
+		attribute='t{:03d}_'.format(idx+1)+label+'Name'	
+
+	
+	a = selfseq_dev.read_attribute(attribute).value
+	
+	if len(a) < 5:
+		raise NameError('Too short device name')
+
+	return a.rpartition('/')[0],a.rpartition('/')[2];  
+
+#Plot a figure for debugging purposes 
+#plt.ion() ## Note this correction
+#fig=plt.figure()
+
+# Initialize variables
+num_sensors = 0
+num_targets = 0
+old_buffer_length = 0
+last = time.time()
+acquisition_period = 1
+lp_filter = 1
+
+# Connect to the bunchnuber server
+bn_dev = DeviceProxy('srv-tango-srf-01:20000/f/timing/bunchnumber_f')
+
+# Connect to the sequencer
+selfseq_dev = DeviceProxy(sys.argv[1])
+
+# Load default configuration
+if selfseq_dev.InitAtStartupFlag == True:
+	setOptions(sys.argv[1],sys.argv[2])
+
+# Configure sensor devices
+sensor_dev = []
+for x in range(0,MAX_NUM_SENSORS-1):
+	try:
+		devname , cmd = get_devname_cmd(SENSOR_TAG,x)
+		sensor_dev.append(DeviceObj(devname,cmd,x,SENSOR_TAG))
+	except:
+		break
+
+# Configure target devices
+target_dev = []
+for x in range(0,MAX_NUM_TARGETS-1):
+	try:
+		devname , cmd = get_devname_cmd(TARGET_TAG,x)
+		target_dev.append(DeviceObj(devname,cmd,x,TARGET_TAG))
+	except:
+		break
+
+# Set the number of devices
+selfseq_dev.NumSensors = len(sensor_dev)
+selfseq_dev.NumTargets = len(target_dev)
+
+
+while 1:
+
+	# calculate driftless repetition period
+	next1 = last + acquisition_period
+	sleep_time = next1 - time.time();
+	if (sleep_time > 0):
+		time.sleep(sleep_time)  # it's ok to sleep negative time
+	last = next1
+
+	start_acquisition_time = time.time();
+
+	buffer_length = selfseq_dev.read_attribute('BufferLength').value
+	acquisition_length = selfseq_dev.read_attribute('AcquisitionLength').value	
+	valid_samples_perc = selfseq_dev.read_attribute('ValidSamplesPerc').value 	
+	acquisition_period = selfseq_dev.read_attribute('AcquisitionPeriod').value
+	pause_flag = selfseq_dev.read_attribute('PauseFlag').value
+	abort_flag = selfseq_dev.read_attribute('AbortFlag').value
+	lp_filter = selfseq_dev.read_attribute('LPFilter').value
+	cur_state = selfseq_dev.read_attribute('State')
+	if cur_state.value != 10:  # RUNNING
+		break
+
+
+	# exit from acquisition
+	if abort_flag:
+		break
+
+	# pause acquisition
+	while pause_flag:
+		pause_flag = selfseq_dev.read_attribute('PauseFlag').value
+		time.sleep(1)	
+
+	# get sensor configuration
+	for dev in sensor_dev:
+		try:
+			dev.get_configuration(buffer_length, acquisition_length, valid_samples_perc)
+		except:
+			pass
+	# get target configuration
+	for dev in target_dev:
+		try:
+			dev.get_configuration(buffer_length, acquisition_length, valid_samples_perc)
+		except:
+			pass
+
+	# calculate start - end bunchnumbers
+	# set the margin to two samples due the sample_shift factor that can change between -1 and 1
+	bn_start = bn_dev.BunchNumber - acquisition_length - 2
+	bn_end = bn_start + acquisition_length - 2
+
+	# acquire sensor data
+	for dev in sensor_dev:
+		try:
+			dev.get_data(bn_start, bn_end)
+		except:
+			pass
+	# acquire target data
+	for dev in target_dev:
+		try:
+			dev.get_data(bn_start, bn_end)
+		except:
+			pass
+
+	end_acquisition_time = time.time();
+
+	for dev in sensor_dev:
+		dev.filter_data()
+		
+	for dev in target_dev:
+		dev.filter_data()
+
+
+#	for dev in sensor_dev:
+#		dev.set_polling_state()
+		
+#	for dev in target_dev:
+#		dev.set_polling_state()
+		
+	for dev in sensor_dev:
+		dev.process_stats()	
+		
+	for dev in target_dev:
+		dev.process_stats()	
+		
+	for dev in sensor_dev:
+		dev.process_corr()	
+
+	for dev in target_dev:
+		dev.process_corr()
+
+	for dev in sensor_dev:
+		dev.set_sequencer()
+
+	for dev in target_dev:
+		dev.set_sequencer()
+
+	end_processing_time = time.time()
+
+	acquisition_time = end_acquisition_time - start_acquisition_time
+	processing_time = end_processing_time - end_acquisition_time
+
+	selfseq_dev.write_attribute('AcquisitionTime',acquisition_time)
+	selfseq_dev.write_attribute('ProcessingTime',processing_time)
+	selfseq_dev.write_attribute('IdleTime',acquisition_period - (processing_time+acquisition_time))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+