diff --git a/ci-scripts/cls_module_ue.py b/ci-scripts/cls_module_ue.py
index dcf75a34fcd616a111cc3c98fc25f1e9281706dd..7fb7882b8da646355efabc1bc934004075ed25c5 100644
--- a/ci-scripts/cls_module_ue.py
+++ b/ci-scripts/cls_module_ue.py
@@ -28,177 +28,197 @@
 import os
 import sys
 import logging
-#to create a SSH object locally in the methods
-import sshconnection
 #time.sleep
 import time
-
-
 import re
 import subprocess
-
 from datetime import datetime
+import yaml
 
 #for log rotation mgt
 import cls_log_mgt
+import cls_cmd
 
 class Module_UE:
 
-	def __init__(self,Module):
-		#create attributes as in the Module dictionary
-		for k, v in Module.items():
-			setattr(self, k, v)
-		self.UEIPAddress = ""
-		#dictionary linking command names and related module scripts
-		self.cmd_dict= {"wup": self.WakeupScript,"detach":self.DetachScript}#dictionary of function scripts
-		self.ue_trace=''		
-
-
+	def __init__(self, module_name, filename="ci_ueinfra.yaml"):
+		with open(filename, 'r') as f:
+			all_ues = yaml.load(f, Loader=yaml.FullLoader)
+			m = all_ues.get(module_name)
+			if m is None:
+				raise Exception(f'no such module name "{module_name}" in "{filename}"')
+			self.module_name = module_name
+			self.host = m['Host']
+			self.cmd_dict = {
+				"attach": m.get('AttachScript'),
+				"detach": m.get('DetachScript'),
+				"initialize": m.get('InitScript'),
+				"terminate": m.get('TermScript'),
+				"getNetwork": m.get('UENetworkScript'),
+				"check": m.get('CheckStatusScript'),
+				"dataEnable": m.get('DataEnableScript'),
+				"dataDisable": m.get('DataDisableScript'),
+			}
+			self.interface = m.get('IF')
+			self.MTU = m.get('MTU')
+			self.trace = m.get('trace') == True
+			self.logStore = m.get('LogStore')
+			self.cmd_prefix = m.get('CmdPrefix')
+			logging.info(f'initialized UE {self.module_name}@{self.host} from {filename}')
+
+	def __str__(self):
+		return f"{self.module_name}@{self.host} [IP: {self.getIP()}]"
+
+	def __repr__(self):
+		return self.__str__()
+
+	def _command(self, cmd, silent = False):
+		if cmd is None:
+			raise Exception("no command provided")
+		if self.host == "" or self.host == "localhost":
+			c = cls_cmd.LocalCmd()
+		else:
+			c = cls_cmd.RemoteCmd(self.host)
+		response = c.run(cmd, silent=silent)
+		c.close()
+		return response.stdout
 
 #-----------------$
 #PUBLIC Methods$
 #-----------------$
 
-	#this method checks if the specified Process is running on the server hosting the module
-	#if not it will be started
-	def CheckCMProcess(self,CNType):
-		HOST=self.HostUsername+'@'+self.HostIPAddress
-		COMMAND="ps aux | grep --colour=never " + self.Process['Name'] + " | grep -v grep "
-		logging.debug(COMMAND)
-		ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
-		result = ssh.stdout.readlines()
-		if len(result)!=0:
-			logging.debug(self.Process['Name'] + " process found")
-			return True 
-		else:#start process and check again  
-			logging.debug(self.Process['Name'] + " process NOT found")
-			#starting the process
-			logging.debug('Starting ' + self.Process['Name'])
-			mySSH = sshconnection.SSHConnection()
-			mySSH.open(self.HostIPAddress, self.HostUsername, self.HostPassword)
-			mySSH.command('echo ' + self.HostPassword + ' | sudo -S rm -f /tmp/quectel-cm.log','\$',5)
-			mySSH.command('echo $USER; echo ' + self.HostPassword + ' | nohup sudo -S stdbuf -o0 ' + self.Process['Cmd'] + ' ' +  self.Process['Apn'][CNType]  + ' > /tmp/quectel-cm.log 2>&1 &','\$',5)
-			mySSH.close()
-			#checking the process
+	def initialize(self):
+		if self.trace:
+			raise Exception("UE tracing not implemented yet")
+			self._enableTrace()
+		# we first terminate to make sure the UE has been stopped
+		if self.cmd_dict["detach"]:
+			self._command(self.cmd_dict["detach"], silent=True)
+		self._command(self.cmd_dict["terminate"], silent=True)
+		self._command(self.cmd_dict["initialize"])
+
+	def terminate(self):
+		self._command(self.cmd_dict["terminate"])
+		if self.trace:
+			raise Exception("UE tracing not implemented yet")
+			self._disableTrace()
+			return self._logCollect()
+		return None
+
+	def attach(self, attach_tries = 4, attach_timeout = 60):
+		ip = None
+		while attach_tries > 0:
+			self._command(self.cmd_dict["attach"])
+			timeout = attach_timeout
+			logging.debug("Waiting for IP address to be assigned")
+			while timeout > 0 and not ip:
+				time.sleep(5)
+				timeout -= 5
+				ip = self.getIP()
+			if ip:
+				break
+			logging.warning(f"UE did not receive IP address after {attach_timeout} s, detaching")
+			attach_tries -= 1
+			self._command(self.cmd_dict["detach"])
 			time.sleep(5)
-			HOST=self.HostUsername+'@'+self.HostIPAddress
-			COMMAND="ps aux | grep --colour=never " + self.Process['Name'] + " | grep -v grep "
-			logging.debug(COMMAND)
-			ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
-			result = ssh.stdout.readlines()
-			if len(result)!=0:
-				logging.debug(self.Process['Name'] + " process found")
-				return True
-			else:
-				logging.debug(self.Process['Name'] + " process NOT found")
-				return False 
-
-	#Generic command function, using function pointers dictionary
-	def Command(self,cmd):
+		if ip:
+			logging.debug(f'\u001B[1mUE IP Address is {ip}\u001B[0m')
+		else:
+			logging.debug('\u001B[1;37;41mUE IP Address Not Found!\u001B[0m')
+		return ip
+
+	def detach(self):
+		self._command(self.cmd_dict["detach"])
+
+	def check(self):
+		cmd = self.cmd_dict["check"]
+		if cmd:
+			return self._command(cmd)
+		else:
+			logging.warning(f"requested status check of UE {self.getName()}, but operation is not supported")
+			return f"UE {self.getName()} does not support status checking"
+
+	def dataEnable(self):
+		cmd = self.cmd_dict["dataEnable"]
+		if cmd:
+			self._command(cmd)
+			return True
+		else:
+			message = f"requested enabling data of UE {self.getName()}, but operation is not supported"
+			logging.error(message)
+			return False
+
+	def dataDisable(self):
+		cmd = self.cmd_dict["dataDisable"]
+		if cmd:
+			self._command(cmd)
+			return True
+		else:
+			message = f"requested disabling data of UE {self.getName()}, but operation is not supported"
+			logging.error(message)
+			return False
+
+	def getIP(self):
+		output = self._command(self.cmd_dict["getNetwork"], silent=True)
+		result = re.search('inet (?P<ip>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', output)
+		if result and result.group('ip'):
+			ip = result.group('ip')
+			return ip
+		return None
+
+	def checkMTU(self):
+		output = self._command(self.cmd_dict["getNetwork"], silent=True)
+		result = re.search('mtu (?P<mtu>[0-9]+)', output)
+		if result and result.group('mtu') and int(result.group('mtu')) == self.MTU:
+			logging.debug('\u001B[1mUE Module NIC MTU is ' + str(self.MTU) + ' as expected\u001B[0m')
+			return True
+		else:
+			logging.debug('\u001B[1;37;41m Incorrect Module NIC MTU or MTU not found! Expected: ' + str(self.MTU) + '\u001B[0m')
+			return False
+
+	def getName(self):
+		return self.module_name
+
+	def getIFName(self):
+		return self.interface
+
+	def getHost(self):
+		return self.host
+
+	def getCmdPrefix(self):
+		return self.cmd_prefix if self.cmd_prefix else ""
+
+	def _enableTrace(self):
+		raise Exception("not implemented")
 		mySSH = sshconnection.SSHConnection()
 		mySSH.open(self.HostIPAddress, self.HostUsername, self.HostPassword)
-		mySSH.command('echo ' + self.HostPassword + ' | sudo -S python3 ' + self.cmd_dict[cmd],'\$',10)
-		time.sleep(5)
-		logging.debug("Module "+ cmd)
+		#delete old artifacts
+		mySSH.command('echo ' + ' '  + ' | sudo -S rm -rf ci_qlog','\$',5)
+		#start Trace, artifact is created in home dir
+		mySSH.command('echo $USER; nohup sudo -E QLog/QLog -s ci_qlog -f NR5G.cfg > /dev/null 2>&1 &','\$', 5)
 		mySSH.close()
 
-
-	#this method retrieves the Module IP address (not the Host IP address) 
-	def GetModuleIPAddress(self):
-		HOST=self.HostUsername+'@'+self.HostIPAddress
-		response= []
-		tentative = 8
-		while (len(response)==0) and (tentative>0):
-			COMMAND="ip a show dev " + self.UENetwork + " | grep --colour=never inet | grep " + self.UENetwork
-			if tentative == 8:
-				logging.debug(COMMAND)
-			ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
-			response = ssh.stdout.readlines()
-			tentative-=1
-			time.sleep(2)
-		if (tentative==0) and (len(response)==0):
-			logging.debug('\u001B[1;37;41m Module IP Address Not Found! Time expired \u001B[0m')
-			return -1
-		else: #check response
-			result = re.search('inet (?P<moduleipaddress>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', response[0].decode("utf-8") )
-			if result is not None: 
-				if result.group('moduleipaddress') is not None: 
-					self.UEIPAddress = result.group('moduleipaddress')
-					logging.debug('\u001B[1mUE Module IP Address is ' + self.UEIPAddress + '\u001B[0m')
-					return 0
-				else:
-					logging.debug('\u001B[1;37;41m Module IP Address Not Found! \u001B[0m')
-					return -1
-			else:
-				logging.debug('\u001B[1;37;41m Module IP Address Not Found! \u001B[0m')
-				return -1
-
-	def CheckModuleMTU(self):
-		HOST=self.HostUsername+'@'+self.HostIPAddress
-		response= []
-		tentative = 3 
-		while (len(response)==0) and (tentative>0):
-			COMMAND="ip a show dev " + self.UENetwork + " | grep --colour=never mtu"
-			logging.debug(COMMAND)
-			ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
-			response = ssh.stdout.readlines()
-			tentative-=1
-			time.sleep(10)
-		if (tentative==0) and (len(response)==0):
-			logging.debug('\u001B[1;37;41m Module NIC MTU Not Found! Time expired \u001B[0m')
-			return -1
-		else: #check response
-			result = re.search('mtu (?P<mtu>[0-9]+)', response[0].decode("utf-8") )
-			if result is not None: 
-				if (result.group('mtu') is not None) and (str(result.group('mtu'))==str(self.MTU)) : 
-					logging.debug('\u001B[1mUE Module NIC MTU is ' + str(self.MTU) + ' as expected\u001B[0m')
-					return 0
-				else:
-					logging.debug('\u001B[1;37;41m Incorrect Module NIC MTU ' + str(result.group('mtu')) + '! Expected : ' + str(self.MTU) + '\u001B[0m')
-					return -1
-			else:
-				logging.debug('\u001B[1;37;41m Module NIC MTU Not Found! \u001B[0m')
-				return -1
-
-	def EnableTrace(self):
-		if self.ue_trace=="yes":
-			mySSH = sshconnection.SSHConnection()
-			mySSH.open(self.HostIPAddress, self.HostUsername, self.HostPassword)
-			#delete old artifacts
-			mySSH.command('echo ' + self.HostPassword + ' | sudo -S rm -rf ci_qlog','\$',5)
-			#start Trace, artifact is created in home dir
-			mySSH.command('echo $USER; nohup sudo -E QLog/QLog -s ci_qlog -f NR5G.cfg > /dev/null 2>&1 &','\$', 5)
-			mySSH.close()
-
-	def DisableTrace(self):
+	def _disableTrace(self):
+		raise Exception("not implemented")
 		mySSH = sshconnection.SSHConnection()
 		mySSH.open(self.HostIPAddress, self.HostUsername, self.HostPassword)
-		mySSH.command('echo ' + self.HostPassword + ' | sudo -S killall --signal=SIGINT *QLog*', '\$',5)
+		mySSH.command('echo ' + ' '  + ' | sudo -S killall --signal=SIGINT *QLog*', '\$',5)
 		mySSH.close()
 
 
-	def DisableCM(self):
+	def _logCollect(self):
+		raise Exception("not implemented")
 		mySSH = sshconnection.SSHConnection()
 		mySSH.open(self.HostIPAddress, self.HostUsername, self.HostPassword)
-		mySSH.command('echo ' + self.HostPassword + ' | sudo -S killall --signal SIGKILL *'+self.Process['Name']+'*', '\$', 5)
+		#archive qlog to USB stick in /media/usb-drive/ci_qlogs with datetime suffix
+		now=datetime.now()
+		now_string = now.strftime("%Y%m%d-%H%M")
+		source='ci_qlog'
+		destination= self.LogStore + '/ci_qlog_'+now_string+'.zip'
+		#qlog artifact is zipped into the target folder
+		mySSH.command('echo $USER; echo ' + ' '  + ' | nohup sudo -S zip -r '+destination+' '+source+' > /dev/null 2>&1 &','\$', 10)
 		mySSH.close()
-
-
-	def LogCollect(self):
-		if self.ue_trace=="yes":
-			mySSH = sshconnection.SSHConnection()
-			mySSH.open(self.HostIPAddress, self.HostUsername, self.HostPassword)
-			#archive qlog to USB stick in /media/usb-drive/ci_qlogs with datetime suffix
-			now=datetime.now()
-			now_string = now.strftime("%Y%m%d-%H%M")
-			source='ci_qlog'
-			destination= self.LogStore + '/ci_qlog_'+now_string+'.zip'
-			#qlog artifact is zipped into the target folder
-			mySSH.command('echo $USER; echo ' + self.HostPassword + ' | nohup sudo -S zip -r '+destination+' '+source+' > /dev/null 2>&1 &','\$', 10)
-			mySSH.close()
-			#post action : log cleaning to make sure enough space is reserved for the next run
-			Log_Mgt=cls_log_mgt.Log_Mgt(self.HostUsername,self.HostIPAddress, self.HostPassword, self.LogStore)
-			Log_Mgt.LogRotation()
-		else:
-			destination=""
+		#post action : log cleaning to make sure enough space is reserved for the next run
+		Log_Mgt=cls_log_mgt.Log_Mgt(self.HostUsername,self.HostIPAddress, self.HostPassword, self.LogStore)
 		return destination
+