Example #1
0
class Framework(object):
	"""
	This is the main instance of the framework.  It contains and
	manages the serial connection as well as all of the loaded
	modules.
	"""
	def __init__(self, stdout=None):
		self.modules = {}
		self.__package__ = '.'.join(self.__module__.split('.')[:-1])
		package_path = importlib.import_module(self.__package__).__path__[0]  # that's some python black magic trickery for you
		if stdout is None:
			stdout = sys.stdout
		self.stdout = stdout
		self.logger = logging.getLogger(self.__package__ + '.' + self.__class__.__name__.lower())

		self.directories = Namespace()
		self.directories.user_data = os.path.expanduser('~') + os.sep + '.termineter' + os.sep
		self.directories.modules_path = package_path + os.sep + 'modules' + os.sep
		self.directories.data_path = package_path + os.sep + 'data' + os.sep
		if not os.path.isdir(self.directories.data_path):
			self.logger.critical('path to data not found')
			raise FrameworkConfigurationError('path to data not found')
		if not os.path.isdir(self.directories.user_data):
			os.mkdir(self.directories.user_data)

		self.serial_connection = None
		self.__serial_connected__ = False

		# setup logging stuff
		main_file_handler = logging.handlers.RotatingFileHandler(self.directories.user_data + self.__package__ + '.log', maxBytes=262144, backupCount=5)
		main_file_handler.setLevel(logging.DEBUG)
		main_file_handler.setFormatter(logging.Formatter("%(asctime)s %(name)-50s %(levelname)-10s %(message)s"))
		logging.getLogger('').addHandler(main_file_handler)

		# setup and configure options
		# Whether or not these are 'required' is really enforced by the individual
		# modules get_missing_options method and by which options they require based
		# on their respective types.  See framework/templates.py for more info.
		self.options = Options(self.directories)
		self.options.add_boolean('USECOLOR', 'enable color on the console interface', default=False)
		self.options.add_string('CONNECTION', 'serial connection string')
		self.options.add_string('USERNAME', 'serial username', default='0000')
		self.options.add_integer('USERID', 'serial userid', default=0)
		self.options.add_string('PASSWORD', 'serial c12.18 password', default='00000000000000000000')
		self.options.add_boolean('PASSWORDHEX', 'if the password is in hex', default=True)
		self.advanced_options = AdvancedOptions(self.directories)
		self.advanced_options.add_boolean('AUTOCONNECT', 'automatically handle connections for modules', default=True)
		self.advanced_options.add_integer('BAUDRATE', 'serial connection baud rate', default=9600)
		self.advanced_options.add_integer('BYTESIZE', 'serial connection byte size', default=serial.EIGHTBITS)
		self.advanced_options.add_boolean('CACHETBLS', 'cache certain read-only tables', default=True)
		self.advanced_options.set_callback('CACHETBLS', self.__opt_callback_set_table_cache_policy)
		self.advanced_options.add_integer('STOPBITS', 'serial connection stop bits', default=serial.STOPBITS_ONE)
		self.advanced_options.add_integer('NBRPKTS', 'c12.18 maximum packets for reassembly', default=2)
		self.advanced_options.add_integer('PKTSIZE', 'c12.18 maximum packet size', default=512)
		if sys.platform.startswith('linux'):
			self.options.set_option('USECOLOR', 'True')

		# check and configure rfcat stuff
		self.rfcat_available = False
		try:
			import rflib
		except ImportError:
			self.logger.info('the rfcat library is not available, it can be found at https://code.google.com/p/rfcat/')
		else:
			self.logger.info('the rfcat library is available')
			self.rfcat_available = True
			# init the values to be used
			self.rfcat_connection = None
			self.__rfcat_connected__ = False
			self.is_rfcat_connected = lambda: self.__rfcat_connected__
			# self.options.add_integer('RFCATIDX', 'the rfcat device to use', default = 0)

		# start loading modules
		modules_path = self.directories.modules_path
		self.logger.debug('searching for modules in: ' + modules_path)
		self.current_module = None
		if not os.path.isdir(modules_path):
			self.logger.critical('path to modules not found')
			raise FrameworkConfigurationError('path to modules not found')
		for module_path in FileWalker(modules_path, absolute_path=True, skip_dirs=True):
			module_path = module_path.replace(os.path.sep, '/')
			if not module_path.endswith('.py'):
				continue
			module_path = module_path[len(modules_path):-3]
			module_name = module_path.split(os.path.sep)[-1]
			if module_name.startswith('__'):
				continue
			if module_name.lower() != module_name:
				continue
			if module_path.startswith('rfcat') and not self.rfcat_available:
				self.logger.debug('skipping module: ' + module_path + ' because rfcat is not available')
				continue
			# looks good, proceed to load
			self.logger.debug('loading module: ' + module_path)
			try:
				module_instance = self.import_module(module_path)
			except FrameworkRuntimeError:
				continue
			if not isinstance(module_instance, TermineterModule):
				self.logger.error('module: ' + module_path + ' is not derived from the TermineterModule class')
				continue
			# if isinstance(module_instance, TermineterModuleRfcat) and not self.rfcat_available:
			# 	self.logger.debug('skipping module: ' + module_path + ' because rfcat is not available')
			#	continue
			if not hasattr(module_instance, 'run'):
				self.logger.critical('module: ' + module_path + ' has no run() method')
				raise FrameworkRuntimeError('module: ' + module_path + ' has no run() method')
			if not isinstance(module_instance.options, Options) or not isinstance(module_instance.advanced_options, Options):
				self.logger.critical('module: ' + module_path + ' options and advanced_options must be Options instances')
				raise FrameworkRuntimeError('options and advanced_options must be Options instances')
			module_instance.name = module_name
			module_instance.path = module_path
			self.modules[module_path] = module_instance
		self.logger.info("successfully loaded {0:,} modules into the framework".format(len(self.modules)))
		return

	def __repr__(self):
		return '<' + self.__class__.__name__ + ' Loaded Modules: ' + str(len(self.modules)) + ', Serial Connected: ' + str(self.is_serial_connected()) + ' >'

	def reload_module(self, module_path=None):
		"""
		Reloads a module into the framework.  If module_path is not
		specified, then the curent_module variable is used.  Returns True
		on success, False on error.

		@type module_path: String
		@param module_path: The name of the module to reload
		"""
		if module_path is None:
			if self.current_module is not None:
				module_path = self.current_module.path
			else:
				self.logger.warning('must specify module if not module is currently being used')
				return False
		if not module_path in self.modules.keys():
			self.logger.error('invalid module requested for reload')
			raise FrameworkRuntimeError('invalid module requested for reload')

		self.logger.info('reloading module: ' + module_path)
		module_instance = self.import_module(module_path, reload_module=True)
		if not isinstance(module_instance, TermineterModule):
			self.logger.error('module: ' + module_path + ' is not derived from the TermineterModule class')
			raise FrameworkRuntimeError('module: ' + module_path + ' is not derived from the TermineterModule class')
		if not hasattr(module_instance, 'run'):
			self.logger.error('module: ' + module_path + ' has no run() method')
			raise FrameworkRuntimeError('module: ' + module_path + ' has no run() method')
		if not isinstance(module_instance.options, Options) or not isinstance(module_instance.advanced_options, Options):
			self.logger.error('module: ' + module_path + ' options and advanced_options must be Options instances')
			raise FrameworkRuntimeError('options and advanced_options must be Options instances')
		module_instance.name = module_path.split('/')[-1]
		module_instance.path = module_path
		self.modules[module_path] = module_instance
		if self.current_module is not None:
			if self.current_module.path == module_instance.path:
				self.current_module = module_instance
		return True

	def run(self, module=None):
		if not isinstance(module, TermineterModule) and not isinstance(self.current_module, TermineterModule):
			raise FrameworkRuntimeError('either the module or the current_module must be sent')
		if module is None:
			module = self.current_module
		if isinstance(module, TermineterModuleOptical):
			if not self.is_serial_connected:
				raise FrameworkRuntimeError('the serial interface is disconnected')

			try:
				self.serial_get()
			except Exception as error:
				self.print_exception(error)
				return
			if module.require_connection:
				if self.advanced_options['AUTOCONNECT']:
					if not self.is_serial_connected():
						try:
							self.serial_connect()
						except Exception as error:
							self.print_exception(error)
							return
						self.print_good('Successfully connected and the device is responding')
					if module.attempt_login and not self.serial_login():
						self.logger.warning('meter login failed, some tables may not be accessible')

		self.logger.info('running module: ' + module.path)
		try:
			result = module.run()
		finally:
			if isinstance(module, TermineterModuleOptical) and self.serial_connection and self.advanced_options['AUTOCONNECT']:
				self.serial_connection.stop()
		return result

	@property
	def use_colors(self):
		return self.options['USECOLOR']

	@use_colors.setter
	def use_colors(self, value):
		self.options.set_option('USECOLOR', str(value))

	def get_module_logger(self, name):
		"""
		This returns a logger for individual modules to allow them to be
		inherited from the framework and thus be named appropriately.

		@type name: String
		@param name: The name of the module requesting the logger
		"""
		return logging.getLogger(self.__package__ + '.modules.' + name)

	def import_module(self, module_path, reload_module=False):
		module = self.__package__ + '.modules.' + module_path.replace('/', '.')
		try:
			module = importlib.import_module(module)
			if reload_module:
				reload(module)
			module_instance = module.Module(self)
		except Exception as err:
			message = 'failed to load module: ' + module_path
			if isinstance(err, SyntaxError):
				message += ', ' + err.msg + ' line number: ' + str(err.lineno)
			self.logger.error(message)
			raise FrameworkRuntimeError(message)
		return module_instance

	def print_exception(self, error):
		message = 'Caught ' + error.__class__.__name__ + ': ' + str(error)
		self.logger.error(message, exc_info=True)
		self.print_error(message)

	def print_error(self, message):
		if self.options['USECOLOR']:
			self.stdout.write('\033[1;31m[-] \033[1;m' + (os.linesep + '\033[1;31m[-] \033[1;m').join(message.split(os.linesep)) + os.linesep)
		else:
			self.stdout.write('[-] ' + (os.linesep + '[-] ').join(message.split(os.linesep)) + os.linesep)
		self.stdout.flush()

	def print_good(self, message):
		if self.options['USECOLOR']:
			self.stdout.write('\033[1;32m[+] \033[1;m' + (os.linesep + '\033[1;32m[+] \033[1;m').join(message.split(os.linesep)) + os.linesep)
		else:
			self.stdout.write('[+] ' + (os.linesep + '[+] ').join(message.split(os.linesep)) + os.linesep)
		self.stdout.flush()

	def print_line(self, message):
		self.stdout.write(message + os.linesep)
		self.stdout.flush()

	def print_status(self, message):
		if self.options['USECOLOR']:
			self.stdout.write('\033[1;34m[*] \033[1;m' + (os.linesep + '\033[1;34m[*] \033[1;m').join(message.split(os.linesep)) + os.linesep)
		else:
			self.stdout.write('[*] ' + (os.linesep + '[*] ').join(message.split(os.linesep)) + os.linesep)
		self.stdout.flush()

	def print_hexdump(self, data):
		data_len = len(data)
		i = 0
		while i < data_len:
			self.stdout.write("{0:04x}    ".format(i))
			for j in range(16):
				if i + j < data_len:
					self.stdout.write("{0:02x} ".format(data[i + j]))
				else:
					self.stdout.write('   ')
				if j % 16 == 7:
					self.stdout.write(' ')
			self.stdout.write('   ')
			r = ''
			for j in data[i:i + 16]:
				if 32 < j < 128:
					r += chr(j)
				else:
					r += '.'
			self.stdout.write(r + os.linesep)
			i += 16
		self.stdout.flush()

	def is_serial_connected(self):
		"""
		Returns True if the serial interface is connected.
		"""
		return self.__serial_connected__

	def serial_disconnect(self):
		"""
		Closes the serial connection to the meter and disconnects from the
		device.
		"""
		if self.__serial_connected__:
			try:
				self.serial_connection.close()
			except C1218IOError as error:
				self.logger.error('caught C1218IOError: ' + str(error))
			except SerialException as error:
				self.logger.error('caught SerialException: ' + str(error))
			self.__serial_connected__ = False
			self.logger.warning('the serial interface has been disconnected')
		return True

	def serial_get(self):
		"""
		Create the serial connection from the framework settings and return
		it, setting the framework instance in the process.
		"""
		frmwk_c1218_settings = {
			'nbrpkts': self.advanced_options['NBRPKTS'],
			'pktsize': self.advanced_options['PKTSIZE']
		}

		frmwk_serial_settings = get_default_serial_settings()
		frmwk_serial_settings['baudrate'] = self.advanced_options['BAUDRATE']
		frmwk_serial_settings['bytesize'] = self.advanced_options['BYTESIZE']
		frmwk_serial_settings['stopbits'] = self.advanced_options['STOPBITS']

		self.logger.info('opening serial device: ' + self.options['CONNECTION'])
		try:
			self.serial_connection = Connection(self.options['CONNECTION'], c1218_settings=frmwk_c1218_settings, serial_settings=frmwk_serial_settings, enable_cache=self.advanced_options['CACHETBLS'])
		except Exception as error:
			self.logger.error('could not open the serial device')
			raise error
		return self.serial_connection

	def serial_connect(self):
		"""
		Connect to the serial device and then verifies that the meter is
		responding.  Once the serial device is opened, this function attempts
		to retreive the contents of table #0 (GEN_CONFIG_TBL) to configure
		the endianess it will use.  Returns True on success.
		"""
		username = self.options['USERNAME']
		userid = self.options['USERID']
		if len(username) > 10:
			self.logger.error('username cannot be longer than 10 characters')
			raise FrameworkConfigurationError('username cannot be longer than 10 characters')
		if not (0 <= userid <= 0xffff):
			self.logger.error('user id must be between 0 and 0xffff')
			raise FrameworkConfigurationError('user id must be between 0 and 0xffff')

		self.serial_get()
		try:
			self.serial_connection.start()
			if not self.serial_connection.login(username, userid):
				self.logger.error('the meter has rejected the username and userid')
				raise FrameworkConfigurationError('the meter has rejected the username and userid')
		except C1218IOError as error:
			self.logger.error('serial connection has been opened but the meter is unresponsive')
			raise error

		try:
			general_config_table = self.serial_connection.get_table_data(0)
		except C1218ReadTableError as error:
			self.logger.error('serial connection as been opened but the general configuration table (table #0) could not be read')
			raise error

		if general_config_table[0] & 1:
			self.logger.info('setting the connection to use big-endian for C12.19 data')
			self.serial_connection.c1219_endian = '>'
		else:
			self.logger.info('setting the connection to use little-endian for C12.19 data')
			self.serial_connection.c1219_endian = '<'

		try:
			self.serial_connection.stop()
		except C1218IOError as error:
			self.logger.error('serial connection has been opened but the meter is unresponsive')
			raise error

		self.__serial_connected__ = True
		self.logger.warning('the serial interface has been connected')
		return True

	def serial_login(self):
		"""
		Attempt to log into the meter over the C12.18 protocol. Returns True on success, False on a failure. This can be
		called by modules in order to login with a username and password configured within the framework instance.
		"""
		username = self.options['USERNAME']
		userid = self.options['USERID']
		password = self.options['PASSWORD']
		if self.options['PASSWORDHEX']:
			hex_regex = re.compile('^([0-9a-fA-F]{2})+$')
			if hex_regex.match(password) is None:
				self.print_error('Invalid characters in password')
				raise FrameworkConfigurationError('invalid characters in password')
			password = binascii.a2b_hex(password)
		if len(username) > 10:
			self.print_error('Username cannot be longer than 10 characters')
			raise FrameworkConfigurationError('username cannot be longer than 10 characters')
		if not (0 <= userid <= 0xffff):
			self.print_error('User id must be between 0 and 0xffff')
			raise FrameworkConfigurationError('user id must be between 0 and 0xffff')
		if len(password) > 20:
			self.print_error('Password cannot be longer than 20 characters')
			raise FrameworkConfigurationError('password cannot be longer than 20 characters')

		if not self.serial_connection.start():
			return False
		if not self.serial_connection.login(username, userid, password):
			return False
		return True

	def __opt_callback_set_table_cache_policy(self, policy):
		if self.is_serial_connected():
			self.serial_connection.set_table_cache_policy(policy)
		return True
Example #2
0
class Framework(object):
	"""
	This is the main instance of the framework.  It contains and 
	manages the serial connection as well as all of the loaded 
	modules.
	"""
	def __init__(self):
		self.modules = { }
		self.__serial_connected__ = False
		self.__package__ = '.'.join(self.__module__.split('.')[:-1])
		package_path = __import__(self.__package__, None, None, ['__path__']).__path__[0]	# that's some python black magic trickery for you
		
		self.directories = Namespace()
		self.directories.user_data = os.path.expanduser('~') + os.sep + '.termineter' + os.sep
		self.directories.modules_path = package_path + os.sep + 'modules' + os.sep
		self.directories.data_path = package_path + os.sep + 'data' + os.sep
		if not os.path.isdir(self.directories.user_data):
			os.mkdir(self.directories.user_data)
		self.serial_connection = None
		self.logger = logging.getLogger(self.__package__ + '.core')
		main_file_handler = logging.handlers.RotatingFileHandler(self.directories.user_data + self.__package__ + '.log', maxBytes = 262144, backupCount = 5)
		main_file_handler.setLevel(logging.DEBUG)
		main_file_handler.setFormatter(logging.Formatter("%(asctime)s %(name)-50s %(levelname)-10s %(message)s"))
		logging.getLogger('').addHandler(main_file_handler)
		self.options = Options(self.directories)
		self.options.addBoolean('USECOLOR', 'enable color on the console interface', default = False)
		self.options.addString('CONNECTION', 'serial connection string', True)
		self.options.addString('USERNAME', 'serial username', default = '0000')
		self.options.addInteger('USERID', 'serial userid', default = 0)
		self.options.addString('PASSWORD', 'serial c12.18 password', default = '00000000000000000000')
		self.options.addBoolean('PASSWORDHEX', 'if the password is in hex', default = True)
		self.advanced_options = Options(self.directories)
		self.advanced_options.addInteger('BAUDRATE', 'serial connection baud rate', default = 9600)
		self.advanced_options.addInteger('BYTESIZE', 'serial connection byte size', default = serial.EIGHTBITS)
		self.advanced_options.addInteger('STOPBITS', 'serial connection stop bits', default = serial.STOPBITS_ONE)
		if sys.platform.startswith('linux'):
			self.options.setOption('USECOLOR', 'True')
		if not os.path.isdir(self.directories.data_path):
			self.logger.critical('path to data not found')
			raise FrameworkConfigurationError('path to data not found')
		modules_path = self.directories.modules_path
		self.logger.debug('searching for modules in: ' + modules_path)
		self.current_module = None
		if not os.path.isdir(modules_path):
			self.logger.critical('path to modules not found')
			raise FrameworkConfigurationError('path to modules not found')
		all_modules = os.listdir(modules_path)
		loadable_modules = os.listdir(modules_path)
		for module in all_modules:										# get rid of ones we don't want to load
			if not module.endswith('.py'):
				loadable_modules.remove(module)
				continue
			if module.startswith('__'):
				loadable_modules.remove(module)
				continue
			if module.lower() != module:
				loadable_modules.remove(module)							# only lower case names please
		del all_modules
		
		for module_name in loadable_modules:
			module_name = module_name[:-3]
			self.logger.debug('loading module: ' + module_name)
			module = __import__(self.__package__ + '.modules.' + module_name, None, None, ['Module'])
			module_instance = module.Module(self.directories)
			if not isinstance(module_instance, module_template):
				self.logger.error('module: ' + module_name + ' is not derived from the module_template class')
				continue
			if not hasattr(module_instance, 'run'):
				self.logger.critical('module: ' + module_name + ' has no run() function')
				raise Exception('module: ' + module_name + ' has no run() function')
			if not isinstance(module_instance.options, Options) or not isinstance(module_instance.advanced_options, Options):
				self.logger.critical('module: ' + module_name + ' options and advanced_options must be Options instances')
				raise Exception('options and advanced_options must be Options instances')
			module_instance.name = module_name
			self.modules[module_name] = module_instance
		self.logger.info('successfully loaded ' + str(len(self.modules)) + ' modules into the framework')
	
	def __repr__(self):
		return '<' + self.__class__.__name__ + ' Loaded Modules: ' + str(len(self.modules)) + ', Serial Connected: ' + str(self.is_serial_connected()) + ' >'
	
	def reload_module(self, module_name = None):
		"""
		Reloads a module into the framework.  If module_name is not
		specified, then the curent_module variable is used.  Returns True
		on success, False on error.
		
		@type module_name: String
		@param module_name: The name of the module to reload
		"""
		if module_name == None:
			if self.current_module != None:
				module_name = self.current_module
			else:
				self.logger.warning('must specify module if not module is currently being used')
				return False
		if not module_name + '.py' in os.listdir(self.directories.modules_path):
			self.logger.error('invalid module name requested for reload')
			return False
		self.logger.info('reloading module: ' + module_name)
		module = __import__(self.__package__ + '.modules.' + module_name, None, None, ['Module'])
		reload(module)
		module_instance = module.Module(self.directories)
		if not isinstance(module_instance, module_template):
			self.logger.error('module: ' + module_name + ' is not derived from the module_template class')
			raise Exception('module: ' + module_name + ' is not derived from the module_template class')
		if not hasattr(module_instance, 'run'):
			self.logger.error('module: ' + module_name + ' has no run() function')
			raise Exception('module: ' + module_name + ' has no run() function')
		if not isinstance(module_instance.options, Options) or not isinstance(module_instance.advanced_options, Options):
			self.logger.error('module: ' + module_name + ' options and advanced_options must be Options instances')
			raise Exception('options and advanced_options must be Options instances')
		module_instance.name = module_name
		self.modules[module_name] = module_instance
		return True
	
	@property
	def use_colors(self):
		return self.options['USECOLOR']
	
	@use_colors.setter
	def use_colors(self, value):
		self.options.setOption('USECOLOR', str(value))
	
	def get_module_logger(self, name):
		"""
		This returns a logger for individual modules to allow them to be
		inherited from the framework and thus be named appropriately.
		
		@type name: String
		@param name: The name of the module requesting the logger
		"""
		return logging.getLogger(self.__package__ + '.modules.' + name)

	def print_error(self, message):
		if self.options['USECOLOR']:
			print '\033[1;31m[-] \033[1;m' + (os.linesep + '\033[1;31m[-] \033[1;m').join(message.split(os.linesep))
		else:
			print '[-] ' + (os.linesep + '[-] ').join(message.split(os.linesep))

	def print_good(self, message):
		if self.options['USECOLOR']:
			print '\033[1;32m[+] \033[1;m' + (os.linesep + '\033[1;32m[+] \033[1;m').join(message.split(os.linesep))
		else:
			print '[+] ' + (os.linesep + '[+] ').join(message.split(os.linesep))

	def print_line(self, message):
		print message

	def print_status(self, message):
		if self.options['USECOLOR']:
			print '\033[1;34m[*] \033[1;m' + (os.linesep + '\033[1;34m[*] \033[1;m').join(message.split(os.linesep))
		else:
			print '[*] ' + (os.linesep + '[*] ').join(message.split(os.linesep))

	def print_hexdump(self, data):
		x = str(data)
		l = len(x)
		i = 0
		while i < l:
			print "%04x  " % i,
			for j in range(16):
				if i+j < l:
					print "%02X" % ord(x[i+j]),
				else:
					print "  ",
				if j%16 == 7:
					print "",
			print " ",
			r = ""
			for j in x[i:i+16]:
				j = ord(j)
				if (j < 32) or (j >= 127):
					r = r + "."
				else:
					r = r + chr(j)
			print r
			i += 16

	def is_serial_connected(self):
		"""
		Returns True if the serial interface is connected.
		"""
		return self.__serial_connected__
	
	def serial_disconnect(self):
		"""
		Closes the serial connection to the meter and disconnects from the
		device.
		"""
		if self.__serial_connected__:
			try:
				self.serial_connection.close()
			except C1218IOError as error:
				self.logger.error('caught C1218IOError: ' + str(error))
			except SerialException as error:
				self.logger.error('caught SerialException: ' + str(error))
			self.__serial_connected__ = False
			self.logger.warning('the serial interface has been disconnected')
		return True

	def serial_connect(self):
		"""
		Connect to the serial device and then verifies that the meter is
		responding.  Once the serial device is opened, this function attempts
		to retreive the contents of table #0 (GEN_CONFIG_TBL) to configure
		the endianess it will use.  Returns True on success.
		"""
		username = self.options['USERNAME']
		userid = self.options['USERID']
		if len(username) > 10:
			self.logger.error('username cannot be longer than 10 characters')
			raise FrameworkConfigurationError('username cannot be longer than 10 characters')
		if not (0 <= userid <= 0xffff):
			self.logger.error('user id must be between 0 and 0xffff')
			raise FrameworkConfigurationError('user id must be between 0 and 0xffff')
		frmwk_serial_settings = {'parity':serial.PARITY_NONE,
			'baudrate': self.advanced_options['BAUDRATE'],
			'bytesize': self.advanced_options['BYTESIZE'],
			'xonxoff': False,
			'interCharTimeout': None,
			'rtscts': False,
			'timeout': 1,
			'stopbits': self.advanced_options['STOPBITS'],
			'dsrdtr': False,
			'writeTimeout': None}
		self.logger.info('opening serial device: ' + self.options['CONNECTION'])
		
		try:
			self.serial_connection = Connection(self.options['CONNECTION'], frmwk_serial_settings, enable_cache = True)
		except Exception as error:
			self.logger.error('could not open the serial device')
			raise error
		
		try:
			self.serial_connection.start()
			if not self.serial_connection.login(username, userid):
				self.logger.error('the meter has rejected the username and userid')
				raise FrameworkConfigurationError('the meter has rejected the username and userid')
		except C1218IOError as error:
			self.logger.error('serial connection has been opened but the meter is unresponsive')
			raise error
		
		try:
			general_config_table = self.serial_connection.getTableData(0)
		except C1218ReadTableError as error:
			self.logger.error('serial connection as been opened but the general configuration table (table #0) could not be read')
			raise error
		
		if (ord(general_config_table[0]) & 1):
			self.logger.info('setting the connection to use big-endian for C1219 data')
			self.serial_connection.c1219_endian = '>'
		else:
			self.logger.info('setting the connection to use little-endian for C1219 data')
			self.serial_connection.c1219_endian = '<'
		
		try:
			self.serial_connection.stop()
		except C1218IOError as error:
			self.logger.error('serial connection has been opened but the meter is unresponsive')
			raise error
		
		self.__serial_connected__ = True
		self.logger.warning('the serial interface has been connected')
		return True
	
	def serial_login(self):
		"""
		Attempt to log into the meter over the C12.18 protocol.  Returns
		True on success, False on a failure.  This can be called by modules
		in order to login with a username and password configured within
		the framework instance.
		"""
		username = self.options['USERNAME']
		userid = self.options['USERID']
		password = self.options['PASSWORD']
		if self.options['PASSWORDHEX']:
			hex_regex = re.compile('^([0-9a-fA-F]{2})+$')
			if hex_regex.match(password) == None:
				self.print_error('Invalid characters in password')
				raise FrameworkConfigurationError('invalid characters in password')
			password = unhexlify(password)
		if len(username) > 10:
			self.print_error('Username cannot be longer than 10 characters')
			raise FrameworkConfigurationError('username cannot be longer than 10 characters')
		if not (0 <= userid <= 0xffff):
			self.print_error('User id must be between 0 and 0xffff')
			raise FrameworkConfigurationError('user id must be between 0 and 0xffff')
		if len(password) > 20:
			self.print_error('Password cannot be longer than 20 characters')
			raise FrameworkConfigurationError('password cannot be longer than 20 characters')
		
		if not self.serial_connection.start():
			return False
		if not self.serial_connection.login(username, userid, password):
			return False
		return True
Example #3
0
class Framework(object):
	"""
	This is the main instance of the framework.  It contains and 
	manages the serial connection as well as all of the loaded 
	modules.
	"""
	def __init__(self, stdout = None):
		self.modules = { }
		self.__package__ = '.'.join(self.__module__.split('.')[:-1])
		package_path = __import__(self.__package__, None, None, ['__path__']).__path__[0]	# that's some python black magic trickery for you
		if stdout == None:
			stdout = sys.stdout
		self.stdout = stdout
		
		self.directories = Namespace()
		self.directories.user_data = os.path.expanduser('~') + os.sep + '.termineter' + os.sep
		self.directories.modules_path = package_path + os.sep + 'modules' + os.sep
		self.directories.data_path = package_path + os.sep + 'data' + os.sep
		if not os.path.isdir(self.directories.data_path):
			self.logger.critical('path to data not found')
			raise FrameworkConfigurationError('path to data not found')
		if not os.path.isdir(self.directories.user_data):
			os.mkdir(self.directories.user_data)
		
		self.serial_connection = None
		self.__serial_connected__ = False
		
		# setup logging stuff
		self.logger = logging.getLogger(self.__package__ + '.' + self.__class__.__name__.lower())
		main_file_handler = logging.handlers.RotatingFileHandler(self.directories.user_data + self.__package__ + '.log', maxBytes = 262144, backupCount = 5)
		main_file_handler.setLevel(logging.DEBUG)
		main_file_handler.setFormatter(logging.Formatter("%(asctime)s %(name)-50s %(levelname)-10s %(message)s"))
		logging.getLogger('').addHandler(main_file_handler)
		
		# setup and configure options
		# Whether or not these are 'required' is really enforced by the individual
		# modules get_missing_options method and by which options they require based
		# on their respective types.  See framework/templates.py for more info.
		self.options = Options(self.directories)
		self.options.addBoolean('USECOLOR', 'enable color on the console interface', default = False)
		self.options.addString('CONNECTION', 'serial connection string')
		self.options.addString('USERNAME', 'serial username', default = '0000')
		self.options.addInteger('USERID', 'serial userid', default = 0)
		self.options.addString('PASSWORD', 'serial c12.18 password', default = '00000000000000000000')
		self.options.addBoolean('PASSWORDHEX', 'if the password is in hex', default = True)
		self.advanced_options = AdvancedOptions(self.directories)
		self.advanced_options.addInteger('BAUDRATE', 'serial connection baud rate', default = 9600)
		self.advanced_options.addInteger('BYTESIZE', 'serial connection byte size', default = serial.EIGHTBITS)
		self.advanced_options.addBoolean('CACHETBLS', 'cache certain read-only tables', default = True)
		self.advanced_options.setCallback('CACHETBLS', self.__optCallbackSetTableCachePolicy__)
		self.advanced_options.addInteger('STOPBITS', 'serial connection stop bits', default = serial.STOPBITS_ONE)
		self.advanced_options.addInteger('NBRPKTS', 'c12.18 maximum packets for reassembly', default = 2)
		self.advanced_options.addInteger('PKTSIZE', 'c12.18 maximum packet size', default = 512)
		if sys.platform.startswith('linux'):
			self.options.setOption('USECOLOR', 'True')
		
		# check and configure rfcat stuff
		self.rfcat_available = False
		try:
			import rflib
			self.logger.info('the rfcat library is available')
			self.rfcat_available = True
		except ImportError:
			self.logger.info('the rfcat library is not available, it can be found at https://code.google.com/p/rfcat/')
			pass
		if self.rfcat_available:
			# init the values to be used
			self.rfcat_connection = None
			self.__rfcat_connected__ = False
			self.is_rfcat_connected = lambda: self.__rfcat_connected__
			# self.options.addInteger('RFCATIDX', 'the rfcat device to use', default = 0)
		
		# start loading modules
		modules_path = self.directories.modules_path
		self.logger.debug('searching for modules in: ' + modules_path)
		self.current_module = None
		if not os.path.isdir(modules_path):
			self.logger.critical('path to modules not found')
			raise FrameworkConfigurationError('path to modules not found')
		for module_path in FileWalker(modules_path, absolute_path = True, skip_dirs = True):
			module_path = module_path.replace(os.path.sep, '/')
			if not module_path.endswith('.py'):
				continue
			module_path = module_path[len(modules_path):-3]
			module_name = module_path.split(os.path.sep)[-1]
			if module_name.startswith('__'):
				continue
			if module_name.lower() != module_name:
				continue
			if module_path.startswith('rfcat') and not self.rfcat_available:
				self.logger.debug('skipping module: ' + module_path + ' because rfcat is not available')
				continue
			# looks good, proceed to load
			self.logger.debug('loading module: ' + module_path)
			try:
				module_instance = self.import_module(module_path)
			except FrameworkRuntimeError:
				continue
			if not isinstance(module_instance, module_template):
				self.logger.error('module: ' + module_path + ' is not derived from the module_template class')
				continue
			# if isinstance(module_instance, rfcat_module_template) and not self.rfcat_available:
			# 	self.logger.debug('skipping module: ' + module_path + ' because rfcat is not available')
			#	continue
			if not hasattr(module_instance, 'run'):
				self.logger.critical('module: ' + module_path + ' has no run() method')
				raise FrameworkRuntimeError('module: ' + module_path + ' has no run() method')
			if not isinstance(module_instance.options, Options) or not isinstance(module_instance.advanced_options, Options):
				self.logger.critical('module: ' + module_path + ' options and advanced_options must be Options instances')
				raise FrameworkRuntimeError('options and advanced_options must be Options instances')
			module_instance.name = module_name
			module_instance.path = module_path
			self.modules[module_path] = module_instance
		self.logger.info('successfully loaded ' + str(len(self.modules)) + ' modules into the framework')
		return
	
	def __repr__(self):
		return '<' + self.__class__.__name__ + ' Loaded Modules: ' + str(len(self.modules)) + ', Serial Connected: ' + str(self.is_serial_connected()) + ' >'
	
	def reload_module(self, module_path = None):
		"""
		Reloads a module into the framework.  If module_path is not
		specified, then the curent_module variable is used.  Returns True
		on success, False on error.
		
		@type module_path: String
		@param module_path: The name of the module to reload
		"""
		if module_path == None:
			if self.current_module != None:
				module_path = self.current_module.path
			else:
				self.logger.warning('must specify module if not module is currently being used')
				return False
		if not module_path in self.modules.keys():
			self.logger.error('invalid module requested for reload')
			raise FrameworkRuntimeError('invalid module requested for reload')
		
		self.logger.info('reloading module: ' + module_path)
		module_instance = self.import_module(module_path, reload_module = True)
		if not isinstance(module_instance, module_template):
			self.logger.error('module: ' + module_path + ' is not derived from the module_template class')
			raise FrameworkRuntimeError('module: ' + module_path + ' is not derived from the module_template class')
		if not hasattr(module_instance, 'run'):
			self.logger.error('module: ' + module_path + ' has no run() method')
			raise FrameworkRuntimeError('module: ' + module_path + ' has no run() method')
		if not isinstance(module_instance.options, Options) or not isinstance(module_instance.advanced_options, Options):
			self.logger.error('module: ' + module_path + ' options and advanced_options must be Options instances')
			raise FrameworkRuntimeError('options and advanced_options must be Options instances')
		module_instance.name = module_path.split('/')[-1]
		module_instance.path = module_path
		self.modules[module_path] = module_instance
		if self.current_module != None:
			if self.current_module.path == module_instance.path:
				self.current_module = module_instance
		return True
	
	def run(self, module = None):
		if not isinstance(module, module_template) and not isinstance(self.current_module, module_template):
			raise FrameworkRuntimeError('either the module or the current_module must be sent')
		if module == None:
			module = self.current_module
		if isinstance(module, optical_module_template):
			if not self.is_serial_connected:
				raise FrameworkRuntimeError('the serial interface is disconnected')
		# if isinstance(module, rfcat_module_template):
		# 	self.rfcat_connect()
		
		result = None
		self.logger.info('running module: ' + module.path)
		try:
			result = module.run()
		except KeyboardInterrupt as error:
			if isinstance(module, optical_module_template):
				self.serial_connection.stop()
			# if isinstance(module, rfcat_module_template):
			# 	self.rfcat_disconnect()
			raise error
		# if isinstance(module, rfcat_module_template):
		# 	self.rfcat_disconnect()
		return result
	
	@property
	def use_colors(self):
		return self.options['USECOLOR']
	
	@use_colors.setter
	def use_colors(self, value):
		self.options.setOption('USECOLOR', str(value))
	
	def get_module_logger(self, name):
		"""
		This returns a logger for individual modules to allow them to be
		inherited from the framework and thus be named appropriately.
		
		@type name: String
		@param name: The name of the module requesting the logger
		"""
		return logging.getLogger(self.__package__ + '.modules.' + name)

	def import_module(self, module_path, reload_module = False):
		try:
			module = __import__(self.__package__ + '.modules.' + module_path.replace('/', '.'), None, None, ['Module'])
			if reload_module:
				reload(module)
			module_instance = module.Module(self)
		except Exception as err:
			message = 'failed to load module: ' + module_path
			if isinstance(err, SyntaxError):
				message += ', ' + err.msg + ' line number: ' + str(err.lineno)
			self.logger.error(message)
			raise FrameworkRuntimeError(message)
		return module_instance

	def print_error(self, message):
		if self.options['USECOLOR']:
			self.stdout.write('\033[1;31m[-] \033[1;m' + (os.linesep + '\033[1;31m[-] \033[1;m').join(message.split(os.linesep)) + os.linesep)
		else:
			self.stdout.write('[-] ' + (os.linesep + '[-] ').join(message.split(os.linesep)) + os.linesep)
		self.stdout.flush()

	def print_good(self, message):
		if self.options['USECOLOR']:
			self.stdout.write('\033[1;32m[+] \033[1;m' + (os.linesep + '\033[1;32m[+] \033[1;m').join(message.split(os.linesep)) + os.linesep)
		else:
			self.stdout.write('[+] ' + (os.linesep + '[+] ').join(message.split(os.linesep)) + os.linesep)
		self.stdout.flush()

	def print_line(self, message):
		self.stdout.write(message + os.linesep)
		self.stdout.flush()

	def print_status(self, message):
		if self.options['USECOLOR']:
			self.stdout.write('\033[1;34m[*] \033[1;m' + (os.linesep + '\033[1;34m[*] \033[1;m').join(message.split(os.linesep)) + os.linesep)
		else:
			self.stdout.write('[*] ' + (os.linesep + '[*] ').join(message.split(os.linesep)) + os.linesep)
		self.stdout.flush()

	def print_hexdump(self, data):
		x = str(data)
		l = len(x)
		i = 0
		while i < l:
			self.stdout.write("%04x   " % i)
			for j in range(16):
				if i+j < l:
					self.stdout.write("%02X " % ord(x[i+j]))
				else:
					self.stdout.write("   ")
				if j%16 == 7:
					self.stdout.write(" ")
			self.stdout.write("  ")
			r = ""
			for j in x[i:i+16]:
				j = ord(j)
				if (j < 32) or (j >= 127):
					r = r + "."
				else:
					r = r + chr(j)
			self.stdout.write(r + os.linesep)
			i += 16
		self.stdout.flush()

	def is_serial_connected(self):
		"""
		Returns True if the serial interface is connected.
		"""
		return self.__serial_connected__
	
	def serial_disconnect(self):
		"""
		Closes the serial connection to the meter and disconnects from the
		device.
		"""
		if self.__serial_connected__:
			try:
				self.serial_connection.close()
			except C1218IOError as error:
				self.logger.error('caught C1218IOError: ' + str(error))
			except SerialException as error:
				self.logger.error('caught SerialException: ' + str(error))
			self.__serial_connected__ = False
			self.logger.warning('the serial interface has been disconnected')
		return True

	def serial_connect(self):
		"""
		Connect to the serial device and then verifies that the meter is
		responding.  Once the serial device is opened, this function attempts
		to retreive the contents of table #0 (GEN_CONFIG_TBL) to configure
		the endianess it will use.  Returns True on success.
		"""
		username = self.options['USERNAME']
		userid = self.options['USERID']
		if len(username) > 10:
			self.logger.error('username cannot be longer than 10 characters')
			raise FrameworkConfigurationError('username cannot be longer than 10 characters')
		if not (0 <= userid <= 0xffff):
			self.logger.error('user id must be between 0 and 0xffff')
			raise FrameworkConfigurationError('user id must be between 0 and 0xffff')
		
		frmwk_c1218_settings = {
			'nbrpkts': self.advanced_options['NBRPKTS'],
			'pktsize': self.advanced_options['PKTSIZE']
		}
		
		frmwk_serial_settings = GetDefaultSerialSettings()
		frmwk_serial_settings['baudrate'] = self.advanced_options['BAUDRATE']
		frmwk_serial_settings['bytesize'] = self.advanced_options['BYTESIZE']
		frmwk_serial_settings['stopbits'] = self.advanced_options['STOPBITS']
		
		self.logger.info('opening serial device: ' + self.options['CONNECTION'])
		
		try:
			self.serial_connection = Connection(self.options['CONNECTION'], c1218_settings = frmwk_c1218_settings, serial_settings = frmwk_serial_settings, enable_cache = self.advanced_options['CACHETBLS'])
		except Exception as error:
			self.logger.error('could not open the serial device')
			raise error
		
		try:
			self.serial_connection.start()
			if not self.serial_connection.login(username, userid):
				self.logger.error('the meter has rejected the username and userid')
				raise FrameworkConfigurationError('the meter has rejected the username and userid')
		except C1218IOError as error:
			self.logger.error('serial connection has been opened but the meter is unresponsive')
			raise error
		
		try:
			general_config_table = self.serial_connection.get_table_data(0)
		except C1218ReadTableError as error:
			self.logger.error('serial connection as been opened but the general configuration table (table #0) could not be read')
			raise error
		
		if (ord(general_config_table[0]) & 1):
			self.logger.info('setting the connection to use big-endian for C1219 data')
			self.serial_connection.c1219_endian = '>'
		else:
			self.logger.info('setting the connection to use little-endian for C1219 data')
			self.serial_connection.c1219_endian = '<'
		
		try:
			self.serial_connection.stop()
		except C1218IOError as error:
			self.logger.error('serial connection has been opened but the meter is unresponsive')
			raise error
		
		self.__serial_connected__ = True
		self.logger.warning('the serial interface has been connected')
		return True
	
	def serial_login(self):
		"""
		Attempt to log into the meter over the C12.18 protocol.  Returns
		True on success, False on a failure.  This can be called by modules
		in order to login with a username and password configured within
		the framework instance.
		"""
		username = self.options['USERNAME']
		userid = self.options['USERID']
		password = self.options['PASSWORD']
		if self.options['PASSWORDHEX']:
			hex_regex = re.compile('^([0-9a-fA-F]{2})+$')
			if hex_regex.match(password) == None:
				self.print_error('Invalid characters in password')
				raise FrameworkConfigurationError('invalid characters in password')
			password = unhexlify(password)
		if len(username) > 10:
			self.print_error('Username cannot be longer than 10 characters')
			raise FrameworkConfigurationError('username cannot be longer than 10 characters')
		if not (0 <= userid <= 0xffff):
			self.print_error('User id must be between 0 and 0xffff')
			raise FrameworkConfigurationError('user id must be between 0 and 0xffff')
		if len(password) > 20:
			self.print_error('Password cannot be longer than 20 characters')
			raise FrameworkConfigurationError('password cannot be longer than 20 characters')
		
		if not self.serial_connection.start():
			return False
		if not self.serial_connection.login(username, userid, password):
			return False
		return True
	
	
	def __optCallbackSetTableCachePolicy__(self, policy):
		if self.is_serial_connected():
			self.serial_connection.set_table_cache_policy(policy)
		return True
Example #4
0
class Framework(object):
    """
	This is the main instance of the framework.  It contains and 
	manages the serial connection as well as all of the loaded 
	modules.
	"""
    def __init__(self):
        self.modules = {}
        self.__serial_connected__ = False
        self.__package__ = '.'.join(self.__module__.split('.')[:-1])
        package_path = __import__(
            self.__package__, None, None,
            ['__path__'
             ]).__path__[0]  # that's some python black magic trickery for you

        self.directories = Namespace()
        self.directories.user_data = os.path.expanduser(
            '~') + os.sep + '.termineter' + os.sep
        self.directories.modules_path = package_path + os.sep + 'modules' + os.sep
        self.directories.data_path = package_path + os.sep + 'data' + os.sep
        if not os.path.isdir(self.directories.user_data):
            os.mkdir(self.directories.user_data)
        self.serial_connection = None
        self.logger = logging.getLogger(self.__package__ + '.core')
        main_file_handler = logging.handlers.RotatingFileHandler(
            self.directories.user_data + self.__package__ + '.log',
            maxBytes=262144,
            backupCount=5)
        main_file_handler.setLevel(logging.DEBUG)
        main_file_handler.setFormatter(
            logging.Formatter(
                "%(asctime)s %(name)-50s %(levelname)-10s %(message)s"))
        logging.getLogger('').addHandler(main_file_handler)
        self.options = Options(self.directories)
        self.options.addBoolean('USECOLOR',
                                'enable color on the console interface',
                                default=False)
        self.options.addString('CONNECTION', 'serial connection string', True)
        self.options.addString('USERNAME', 'serial username', default='0000')
        self.options.addInteger('USERID', 'serial userid', default=0)
        self.options.addString('PASSWORD',
                               'serial c12.18 password',
                               default='00000000000000000000')
        self.options.addBoolean('PASSWORDHEX',
                                'if the password is in hex',
                                default=True)
        self.advanced_options = Options(self.directories)
        self.advanced_options.addInteger('BAUDRATE',
                                         'serial connection baud rate',
                                         default=9600)
        self.advanced_options.addInteger('BYTESIZE',
                                         'serial connection byte size',
                                         default=serial.EIGHTBITS)
        self.advanced_options.addInteger('STOPBITS',
                                         'serial connection stop bits',
                                         default=serial.STOPBITS_ONE)
        if sys.platform.startswith('linux'):
            self.options.setOption('USECOLOR', 'True')
        if not os.path.isdir(self.directories.data_path):
            self.logger.critical('path to data not found')
            raise FrameworkConfigurationError('path to data not found')
        modules_path = self.directories.modules_path
        self.logger.debug('searching for modules in: ' + modules_path)
        self.current_module = None
        if not os.path.isdir(modules_path):
            self.logger.critical('path to modules not found')
            raise FrameworkConfigurationError('path to modules not found')
        all_modules = os.listdir(modules_path)
        loadable_modules = os.listdir(modules_path)
        for module in all_modules:  # get rid of ones we don't want to load
            if not module.endswith('.py'):
                loadable_modules.remove(module)
                continue
            if module.startswith('__'):
                loadable_modules.remove(module)
                continue
            if module.lower() != module:
                loadable_modules.remove(module)  # only lower case names please
        del all_modules

        for module_name in loadable_modules:
            module_name = module_name[:-3]
            self.logger.debug('loading module: ' + module_name)
            module = __import__(self.__package__ + '.modules.' + module_name,
                                None, None, ['Module'])
            module_instance = module.Module(self.directories)
            if not isinstance(module_instance, module_template):
                self.logger.error(
                    'module: ' + module_name +
                    ' is not derived from the module_template class')
                continue
            if not hasattr(module_instance, 'run'):
                self.logger.critical('module: ' + module_name +
                                     ' has no run() function')
                raise Exception('module: ' + module_name +
                                ' has no run() function')
            if not isinstance(module_instance.options,
                              Options) or not isinstance(
                                  module_instance.advanced_options, Options):
                self.logger.critical(
                    'module: ' + module_name +
                    ' options and advanced_options must be Options instances')
                raise Exception(
                    'options and advanced_options must be Options instances')
            module_instance.name = module_name
            self.modules[module_name] = module_instance
        self.logger.info('successfully loaded ' + str(len(self.modules)) +
                         ' modules into the framework')

    def __repr__(self):
        return '<' + self.__class__.__name__ + ' Loaded Modules: ' + str(
            len(self.modules)) + ', Serial Connected: ' + str(
                self.is_serial_connected()) + ' >'

    def reload_module(self, module_name=None):
        """
		Reloads a module into the framework.  If module_name is not
		specified, then the curent_module variable is used.  Returns True
		on success, False on error.
		
		@type module_name: String
		@param module_name: The name of the module to reload
		"""
        if module_name == None:
            if self.current_module != None:
                module_name = self.current_module
            else:
                self.logger.warning(
                    'must specify module if not module is currently being used'
                )
                return False
        if not module_name + '.py' in os.listdir(
                self.directories.modules_path):
            self.logger.error('invalid module name requested for reload')
            return False
        self.logger.info('reloading module: ' + module_name)
        module = __import__(self.__package__ + '.modules.' + module_name, None,
                            None, ['Module'])
        reload(module)
        module_instance = module.Module(self.directories)
        if not isinstance(module_instance, module_template):
            self.logger.error('module: ' + module_name +
                              ' is not derived from the module_template class')
            raise Exception('module: ' + module_name +
                            ' is not derived from the module_template class')
        if not hasattr(module_instance, 'run'):
            self.logger.error('module: ' + module_name +
                              ' has no run() function')
            raise Exception('module: ' + module_name +
                            ' has no run() function')
        if not isinstance(module_instance.options, Options) or not isinstance(
                module_instance.advanced_options, Options):
            self.logger.error(
                'module: ' + module_name +
                ' options and advanced_options must be Options instances')
            raise Exception(
                'options and advanced_options must be Options instances')
        module_instance.name = module_name
        self.modules[module_name] = module_instance
        return True

    @property
    def use_colors(self):
        return self.options['USECOLOR']

    @use_colors.setter
    def use_colors(self, value):
        self.options.setOption('USECOLOR', str(value))

    def get_module_logger(self, name):
        """
		This returns a logger for individual modules to allow them to be
		inherited from the framework and thus be named appropriately.
		
		@type name: String
		@param name: The name of the module requesting the logger
		"""
        return logging.getLogger(self.__package__ + '.modules.' + name)

    def print_error(self, message):
        if self.options['USECOLOR']:
            print '\033[1;31m[-] \033[1;m' + (os.linesep +
                                              '\033[1;31m[-] \033[1;m').join(
                                                  message.split(os.linesep))
        else:
            print '[-] ' + (os.linesep + '[-] ').join(message.split(
                os.linesep))

    def print_good(self, message):
        if self.options['USECOLOR']:
            print '\033[1;32m[+] \033[1;m' + (os.linesep +
                                              '\033[1;32m[+] \033[1;m').join(
                                                  message.split(os.linesep))
        else:
            print '[+] ' + (os.linesep + '[+] ').join(message.split(
                os.linesep))

    def print_line(self, message):
        print message

    def print_status(self, message):
        if self.options['USECOLOR']:
            print '\033[1;34m[*] \033[1;m' + (os.linesep +
                                              '\033[1;34m[*] \033[1;m').join(
                                                  message.split(os.linesep))
        else:
            print '[*] ' + (os.linesep + '[*] ').join(message.split(
                os.linesep))

    def print_hexdump(self, data):
        x = str(data)
        l = len(x)
        i = 0
        while i < l:
            print "%04x  " % i,
            for j in range(16):
                if i + j < l:
                    print "%02X" % ord(x[i + j]),
                else:
                    print "  ",
                if j % 16 == 7:
                    print "",
            print " ",
            r = ""
            for j in x[i:i + 16]:
                j = ord(j)
                if (j < 32) or (j >= 127):
                    r = r + "."
                else:
                    r = r + chr(j)
            print r
            i += 16

    def is_serial_connected(self):
        """
		Returns True if the serial interface is connected.
		"""
        return self.__serial_connected__

    def serial_disconnect(self):
        """
		Closes the serial connection to the meter and disconnects from the
		device.
		"""
        if self.__serial_connected__:
            try:
                self.serial_connection.close()
            except C1218IOError as error:
                self.logger.error('caught C1218IOError: ' + str(error))
            except SerialException as error:
                self.logger.error('caught SerialException: ' + str(error))
            self.__serial_connected__ = False
            self.logger.warning('the serial interface has been disconnected')
        return True

    def serial_connect(self):
        """
		Connect to the serial device and then verifies that the meter is
		responding.  Once the serial device is opened, this function attempts
		to retreive the contents of table #0 (GEN_CONFIG_TBL) to configure
		the endianess it will use.  Returns True on success.
		"""
        username = self.options['USERNAME']
        userid = self.options['USERID']
        if len(username) > 10:
            self.logger.error('username cannot be longer than 10 characters')
            raise FrameworkConfigurationError(
                'username cannot be longer than 10 characters')
        if not (0 <= userid <= 0xffff):
            self.logger.error('user id must be between 0 and 0xffff')
            raise FrameworkConfigurationError(
                'user id must be between 0 and 0xffff')
        frmwk_serial_settings = {
            'parity': serial.PARITY_NONE,
            'baudrate': self.advanced_options['BAUDRATE'],
            'bytesize': self.advanced_options['BYTESIZE'],
            'xonxoff': False,
            'interCharTimeout': None,
            'rtscts': False,
            'timeout': 1,
            'stopbits': self.advanced_options['STOPBITS'],
            'dsrdtr': False,
            'writeTimeout': None
        }
        self.logger.info('opening serial device: ' +
                         self.options['CONNECTION'])

        try:
            self.serial_connection = Connection(self.options['CONNECTION'],
                                                frmwk_serial_settings,
                                                enable_cache=True)
        except Exception as error:
            self.logger.error('could not open the serial device')
            raise error

        try:
            self.serial_connection.start()
            if not self.serial_connection.login(username, userid):
                self.logger.error(
                    'the meter has rejected the username and userid')
                raise FrameworkConfigurationError(
                    'the meter has rejected the username and userid')
        except C1218IOError as error:
            self.logger.error(
                'serial connection has been opened but the meter is unresponsive'
            )
            raise error

        try:
            general_config_table = self.serial_connection.getTableData(0)
        except C1218ReadTableError as error:
            self.logger.error(
                'serial connection as been opened but the general configuration table (table #0) could not be read'
            )
            raise error

        if (ord(general_config_table[0]) & 1):
            self.logger.info(
                'setting the connection to use big-endian for C1219 data')
            self.serial_connection.c1219_endian = '>'
        else:
            self.logger.info(
                'setting the connection to use little-endian for C1219 data')
            self.serial_connection.c1219_endian = '<'

        try:
            self.serial_connection.stop()
        except C1218IOError as error:
            self.logger.error(
                'serial connection has been opened but the meter is unresponsive'
            )
            raise error

        self.__serial_connected__ = True
        self.logger.warning('the serial interface has been connected')
        return True

    def serial_login(self):
        """
		Attempt to log into the meter over the C12.18 protocol.  Returns
		True on success, False on a failure.  This can be called by modules
		in order to login with a username and password configured within
		the framework instance.
		"""
        username = self.options['USERNAME']
        userid = self.options['USERID']
        password = self.options['PASSWORD']
        if self.options['PASSWORDHEX']:
            hex_regex = re.compile('^([0-9a-fA-F]{2})+$')
            if hex_regex.match(password) == None:
                self.print_error('Invalid characters in password')
                raise FrameworkConfigurationError(
                    'invalid characters in password')
            password = unhexlify(password)
        if len(username) > 10:
            self.print_error('Username cannot be longer than 10 characters')
            raise FrameworkConfigurationError(
                'username cannot be longer than 10 characters')
        if not (0 <= userid <= 0xffff):
            self.print_error('User id must be between 0 and 0xffff')
            raise FrameworkConfigurationError(
                'user id must be between 0 and 0xffff')
        if len(password) > 20:
            self.print_error('Password cannot be longer than 20 characters')
            raise FrameworkConfigurationError(
                'password cannot be longer than 20 characters')

        if not self.serial_connection.start():
            return False
        if not self.serial_connection.login(username, userid, password):
            return False
        return True