## @package alindev.py 
#	File containing the device class, to access device registers
#
#	Author = "Manolo Broseta"
#	Copyright = "Copyright 2015, ALBA"
#	Version = "1.1"
#	Email = "mbroseta@cells.es"
#	Status = "Development"
#	History:
#   29/07/2015 - file created
#	20/10/2015 - Several modifications done
#	             Doxygen detailed info added

__author__ = "Manolo Broseta"
__copyright__ = "Copyright 2015, ALBA"
__license__ = "GPLv3 or later"
__version__ = "1.0"
__email__ = "mbroseta@cells.es"
__status__ = "Development"

import os.path, pickle
import logging, logging.handlers

from alin import *

__OFFSET__ = 0x00 #0x100

__DEBUG__ = False

## alinDevice class
#
# Main class that links the device map pickel file created with the coresponding memory area defined in the SDB
# It provides functions to get device information and to read/write attributes of that device
class alinDevice():
	## The constructor.
	#  @param device (not mandatory) Device to controls
	def __init__(self,device=None):
		self._debugLevel = __DEBUG__
		self._devMap = {}
		self.sdbDrv = alinSDB(__OFFSET__)
		self.sdbDrv.getData(__OFFSET__)
		
		self.__device_folder = os.path.abspath(os.path.dirname(__file__))+"/deviceslib/"
		
		# Logging
		self.__logging_file = os.path.abspath(os.path.dirname(__file__))+"/logs/alin.log"
		self._devlogger = logging.getLogger('alinDevice')
		if not len(self._devlogger.handlers):
			self._devlogger.setLevel(logging.DEBUG)
			
			handler = logging.handlers.RotatingFileHandler(self.__logging_file, maxBytes=5000000, backupCount=5)
			handler.setLevel(logging.INFO)
			
			formatter = logging.Formatter('%(asctime)s - %(name)s - %(message)s')
			handler.setFormatter(formatter)
			self._devlogger.addHandler(handler)
		
		if device:
			self.setDevice(device)

	## setDevice(device)
	#  This function reads the memory, searching for the prodcutID ad vendroID specified. When it is found it stores the falue for the memory
	#  address location where the device is located. A pickle file, named as the device, should exits to complete the set fo the Device. The pickle file
	#  contains a dict with all the device registers mapping
	#  @param device Device to read the information. This is a string parametar that should have the following structure:  PorductID:VendorID
	def setDevice(self,devname):
		
		devname = devname.upper()
		pickfile = self.__device_folder+devname
		self.__setLogMessage("setDevice::Reading %s pickle file"%pickfile)

		if not os.path.isfile(pickfile):
			self.__setLogMessage("setDevice::%s device register map not found!!"%devname)
                        return None
		else:
			self.__setLogMessage("setDevice::%s device found!"%devname)
		
		self._devMap = {}
		try:
			with open(pickfile) as output:
				self._devMap = pickle.load(output)
		except Exception, e:
			self.__setLogMessage("setDevice::Problem found while reading %s"%pickfile)		
			print str(e)
			return None
				
		if self._devMap and "vendorid" in self._devMap.keys() and "product" in self._devMap.keys():
			self.vendorId = self._devMap["vendorid"]
			self.__setLogMessage("setDevice::%s getting vendorID....: %s"%(devname,str(self.vendorId)))
			self.product = self._devMap["product"]			
			self.__setLogMessage("setDevice::%s getting product....: %s"%(devname,str(self.product)))
		
			temp = []
			temp = self.sdbDrv.readData()
			for el in temp:
				if (el['interconnect'] == self.sdbDrv.SDB_RECORD_BRIDGE) or\
					(el['interconnect'] == self.sdbDrv.SDB_RECORD_INTERCONNECT) or\
					(el['interconnect'] == self.sdbDrv.SDB_RECORD_DEVICE):
					vendor = ''.join(['%02x'%a for a in el['vendor']])
					device = ''.join(['%02x'%a for a in el['device']])		
					if  ((self.vendorId == int(vendor,16)) and  self.product == int(device,16)):
						self.initAddress = self.sdbDrv.getAddress(el['first_address'])+el['base_address']
						self.__setLogMessage("setDevice::%s init address found...: %s"%(devname,hex(self.initAddress)))
		else:
			self._devMap = {}
			self.__setLogMessage("setDevice::%s Problem found, vendoID and product not found in file"%devname)
			return None
	
		return self._devMap

				
	## getDeviceInfo()
	#  This function returns all mapping information for the corresponding device
	#  @return Dict that contains all device information and mapping
	def getDeviceInfo(self):
		self.__setLogMessage("getDeviceInfo::")		
		return self._devMap
	
	## getDeviceName()
	#  This function returns the device name which is included in the pickle file
	#  @return String that contains the device name
	def getDeviceName(self):
		if self._devMap and "name" in self._devMap.keys():
			self.__setLogMessage("getDeviceName::")		
			return self._devMap["name"]
		else:
			self.__setLogMessage("getDeviceName::Name not availble")		
		return None		
	
	## getDeviceDescription()
	#  This function returns the device description which is included in the pickle file
	#  @return String that contains the description of the device
	def getDeviceDescription(self):
		if self._devMap and "description" in self._devMap.keys():
			self.__setLogMessage("getDeviceDescription::")		
			return self._devMap["description"]
		else:
			self.__setLogMessage("getDeviceDescription::Description not availble")		
		return None

	## getDeviceData()
	#  This function returns all device information included in the pickle file, including device name, description and register mapping
	#  @return List containing all device data
	def getDeviceData(self):
		valueList = []
		rd_data = {}
		rd_data = self.sdbDrv.getDeviceMemory(vend=self.vendorId, prod=self.product)
		if rd_data and self._devMap:
			for el in self._devMap['regs']:
				regname = el.keys()[0]
				address = el[el.keys()[0]]['address']
				position = el[el.keys()[0]]['bit_position']
				size = el[el.keys()[0]]['size']
				
				mask = (2**size - 1) << position
				
				value = rd_data['data'][address]
				value = (value&mask)>>position
				
				valueList.append((regname.upper(),hex(value)))
			self.__setLogMessage("getDeviceData::Read all device data list")
		else:
			self.__setLogMessage("getDeviceData:Not possible to get the data from Memory!!")
		
		return valueList

	## getAttributesList()
	#  This function returns the list of attributes defined in the pickle file
	#  @return List containing the list of attributes defined in the pickle file
	def getAttributesList(self):
		valueList = []
		if self._devMap:
			valueList = [el.keys()[0] for el in self._devMap['regs']]
			self.__setLogMessage("getAttributesList::Attribute list read")
		else:
			self.__setLogMessage("getAttributesList::No data found")
		return valueList

	## writeAttribute(attrName,attrVal)
	#  This function uses the initial device address obtained from the SDB for the device and do the mask wiht the 
	#  information in the pickle file for the register selecter to calculate the proper masked value to write.
	#  Then it uses alinSDB write funciton to apply the corresponding value
	#  @param attrName String containing the attribute name to write
	#  @param attrVal Num with the value to write in the aatribute
	def writeAttribute(self,attrName,attrVal):
		if attrName is not None and self._devMap:
			for el in self._devMap['regs']:
				if attrName.lower() == el.keys()[0].lower():
					address = el[el.keys()[0]]['address']
					sdb_address = self.initAddress+(address*4)
					position = el[el.keys()[0]]['bit_position']
					size = el[el.keys()[0]]['size']
					access = el[el.keys()[0]]['access'].split()[0]

					mask = (2**size - 1) << position
					
					value = self.sdbDrv.readAddress(sdb_address)

					if access == "WRITE_ONLY" or access == "READ_WRITE":
						inv_mask = 0xffffffff - mask
						value = value & inv_mask
						data = (attrVal&(2**size - 1)) << position
						value = (value | data)
						
						self.sdbDrv.writeAddress(sdb_address,value)
						
						self.__setLogMessage("writeAttribute::Attribute %s address: %s value=%s"%(attrName, hex(sdb_address), hex(value)))
					else:
						self.__setLogMessage("writeAttribute::Attribute is %s type"%access)
		else:
			self.__setLogMessage("writeAttribute::Cannot write %s attribute"%attrName)

	## readAttribute(attrName)
	#  This function uses the initial device address obtained from the SDB to read the value using the alinSDB read
	#  functions. Then it uses the information in the pickle file to apply the proper mask and return the masked value
	#  @param attrName String containing the attribute name to write
	#  @return Numeric value for te selected attribute
	def readAttribute(self,attrName):
		value = None
		if attrName is not None and self._devMap:
			for el in self._devMap['regs']:
				if attrName.lower() == el.keys()[0].lower():
					address = el[el.keys()[0]]['address']
					sdb_address = self.initAddress+(address*4)
					position = el[el.keys()[0]]['bit_position']
					size = el[el.keys()[0]]['size']
					access = el[el.keys()[0]]['access'].split()[0]

					mask = (2**size - 1) << position
					value = self.sdbDrv.readAddress(sdb_address)
					value = (value&mask)>>position
					
					self.__setLogMessage("readAttribute::Attribute %s address: %s value=%s"%(attrName, hex(sdb_address), hex(value)))
		else:
			self.__setLogMessage("readAttribute::Cannot read %s attribute"%attrName)
		return value
	
	## readAttribute(attrName)
	#  This function uses the initial device address obtained from the SDB to read the value using the alinSDB read
	#  functions. It uses the information in the pickle file to apply the proper mask and return the masked value and 
	#  return the value and the rest of detailed regiser information.
	#  @param attrName String containing the attribute name to write
	#  @return Dict that contains the register detailed information, including its value
	def getAttributeInfo(self,attrName):
		valueDict = {}
		if attrName is not None and self._devMap:
			for el in self._devMap['regs']:
				if attrName.lower() == el.keys()[0].lower():
					address = el[el.keys()[0]]['address']
					sdb_address = self.initAddress+(address*4)
					position = el[el.keys()[0]]['bit_position']
					size = el[el.keys()[0]]['size']
					access = el[el.keys()[0]]['access'].split()[0]
					desc = el[el.keys()[0]]['description']

					mask = (2**size - 1) << position
					value = self.sdbDrv.readAddress(sdb_address)
					value = (value&mask)>>position
					
					valueDict['name'] = attrName
					valueDict['desc'] = desc
					valueDict['address'] = sdb_address
					valueDict['position'] = position
					valueDict['size'] = size
					valueDict['access'] = access
					valueDict['value'] = value
			self.__setLogMessage("getAttributeInfo::Attribute info %s dict read"%attrName )
		else:
			self.__setLogMessage("getAttributeInfo::Cannot get %s attribute info"%attrName)
		return valueDict

	## setLogEnable(dbg=False)
	#  This function enables the debug information through screen (To log file is always enabled)
	#  @param dbg (not mandatory) enables/disables the log oputput. By default is disabled
	def setLogEnable(self,dbg=False):
		self._debugLevel = dbg
		self.__setLogMessage("setDebugLevel::Debug level set to %s"%self._debugLevel)		
		
	## __setLogMessage(msg) 
	#  Private function that prints a log message to log file and to screen (if enabled)
	#  @param msg Message to print
	def __setLogMessage(self,msg):
		self._devlogger.info(msg)		
		if self._debugLevel: print msg
		
