#!/usr/bin/python

import lib_system
import subprocess
import os
import shutil
import serial
import time
import dbus
import sys
import socket
import ctypes
import lib_network
import glob
from enum import Enum
from collections import namedtuple

CELLULAR_INTERFACE_NAME = "ppp0"

# all stuff containing information on USB modems
class USBModemLocation(Enum):
	Internal = 0
	External = 1
	Auto = 2
USBModemInfo = namedtuple("USBModemInfo", "Location Name VidPid")

"""List of all USB modems supported by the system. Add here any further amazing modem.

Telit modems can not be differentiated easily, since different internal and external
modems with this chipset share the same Vendor-/ProductID. Therefore the 'Auto' Location
was introduced. A modem set to Auto will check if it is connected internally or externally
by looking at the bus the device is actually connected to and use the appropriate reboot
method.

Be also aware that depending on USBCFG the ProductId of a telit modem can change.
Per default/factory-config, telit modems will report as productId 0x0036 (ACM + NCM).
USBCFG value of '2' will be set automatically for Multitech modems by ofono, which
will put the modem into 'enterprise'-/acm-only mode which reports as 0x0035.
USBCFG value of '4' actually sets the Modems into ACM + NCM + Suspend mode, which
reports as 0x0037.
"""
__supported_modems = [
	USBModemInfo(USBModemLocation.Auto, "Telit (Internal or USB)", "1bc7:0035"),
	USBModemInfo(USBModemLocation.Auto, "Telit (Internal or USB)", "1bc7:0036"),
	USBModemInfo(USBModemLocation.Auto, "Telit (Internal or USB)", "1bc7:0037"),
	USBModemInfo(USBModemLocation.External, "External Huawei MS2131", "12d1:1506")
]

class ModemDiagnosticCode(Enum):
	WellConfigured = 0
	ModemIsAbsent = 10
	SeveralModemsArePresent = 11
	OfonodNotRunning = 15
	ModemIsNotRecognized = 20
	SimIsAbsent = 25
	SimIsPinLocked = 30
	SimIsPukLocked = 35
	SimError = 40
	NetworkIsUnregistered = 45
	GPRSNetworkNotRegistered = 50
	APNConnectionFailed = 55
	InternetContextFailed = 60
	ContactServerFailed = 65

# DBus data conversion stuff
_dbus2py = {
	dbus.String : unicode,
	dbus.UInt32 : int,
	dbus.Int32 : int,
	dbus.Int16 : int,
	dbus.UInt16 : int,
	dbus.UInt64 : int,
	dbus.Int64 : int,
	dbus.Byte : int,
	dbus.Boolean : bool,
	dbus.ByteArray : str,
	dbus.ObjectPath : str
}

def __dbus2py(d):
	"""DBus data conversion stuff
	"""
	t = type(d)
	if t in _dbus2py:
		return _dbus2py[t](d)
	if t is dbus.Dictionary:
		return dict([(__dbus2py(k), __dbus2py(v)) for k, v in d.items()])
	if t is dbus.Array and d.signature == "y":
		return "".join([chr(b) for b in d])
	if t is dbus.Array or t is list:
		return [__dbus2py(v) for v in d]
	if t is dbus.Struct or t is tuple:
		return tuple([__dbus2py(v) for v in d])
	return d

def getConfigModemFilePath():
	"""path to the file containing modem configuration file
	"""
	return "/mnt/fcc/config_modem"

def loadConfigModemFile(verbose=True):
	""" return modem configuration file content
		e.g. True, PIN, APN, UserName, Password or False, "", "", "", ""
	"""
	config_modem_file_path = getConfigModemFilePath()
	if verbose:
		print ""	
		print "-Load existing modem configuration from \"%s\"" % (config_modem_file_path)
	
	if os.path.isfile(config_modem_file_path):		 	
		# read modem configuration file
		try:
			with open(config_modem_file_path, 'r') as config_modem_file:
				config_modem_lines = config_modem_file.read().splitlines()
				if len(config_modem_lines) >= 4:
					return True, config_modem_lines[0], config_modem_lines[1], config_modem_lines[2], config_modem_lines[3]
				else:
					message = "File content is corrupted"
		except Exception as e:
			message = "Exception while reading \"%s\": \"%s\"" % (config_modem_file_path, e)
	else:
		message = "No modem configuration"
	if verbose:
		print message
	return False, "","","",""

def saveConfigModemFile(PIN, APN, user_name, password, verbose=False):
	""" save modem configuration into modem configuration file
		e.g. 	PIN
				APN
				UserName
				Password
		in /mnt/fcc/config_modem
	"""	
	config_modem_file_path = getConfigModemFilePath()
	
	if verbose:
		print ""
		print "-Save modem configuration to \"%s\"" % (config_modem_file_path)	
	
	status = False
	try:		
		config_modem_lines = "%s\n%s\n%s\n%s\n" % (PIN, APN, user_name, password)
		
		with open(config_modem_file_path, 'w') as config_modem_file:
			config_modem_file.write(config_modem_lines)
			message = "Configuration saved"
			status = True
	except Exception as e:
		message = "Exception while writing \"%s\": \"%s\"" % (config_modem_file_path, e)
	if verbose:
		print message	
	return status, message

def isModemConfigured():
	if os.path.isfile(getConfigModemFilePath()):
		return True
	return False

def removeConfigModemFile():
	"""Remove configuration file from permanent memory (if any)
	"""	 
	try:
		os.remove(getConfigModemFilePath())
		print "Removed config file %s" % (getConfigModemFilePath())
	except OSError:
		pass

	lib_system.sync()

def listUSBModems(verbose=False):
	""" Return list of supported USB modems currently mounted by the system.
	tuple (count, message, list)
	"""
	
	if verbose:
		print ""
		print "-Check for compatible USB modem(s)"

	count = 0
	mounted_modems = []
	message = "No modem found"
	
	# We search modems mounted by the system	
	for supported_modem in __supported_modems:
		try:
			# check if this modem exists in the system using lsusb command (searched by vendor Id & product Id)
			command = ['lsusb', '-d', supported_modem.VidPid]
			with open(os.devnull, 'w') as shutup:
				subprocess.check_call(command, stdout=shutup, stderr=shutup)
			count += 1
			mounted_modems.append(supported_modem) 
			
			message = "Modem \'%s\' is detected" % (supported_modem.Name)
			if verbose:
				print message
				
		except subprocess.CalledProcessError as e:
			# this exception is raised when lusb commands failed to find a specific device
			# in this case we look for the next one
			pass
		except Exception as e:
			if verbose:
				print "Generic exception while getting USB modem(s):%s" % (e)
			break
	if count > 1:
		message = "Several modems are installed"
	if verbose and count != 1:
		print message

	return count, message, mounted_modems

def sendSerialCommandToModem(command, read_timeout = 1):
	""" Send a command to the modem via serial line. return tuple (status, modem answer or error message)
	Note: As it directly opens modem serial port, this function cannot be called when ofono is running... 
	"""
	if len(command) == 0:
		return False, "Command to send is empty"
	
	try:
		ser = serial.Serial(
			port='/dev/ttyACM0',
			baudrate=115200,
			timeout=read_timeout,	#read timeout
			write_timeout=1,	#write timeout
			parity=serial.PARITY_ODD,
			stopbits=serial.STOPBITS_TWO,
			bytesize=serial.SEVENBITS
		)
	except serial.SerialException as e:
		return False, "Exception while creating serial communication with modem %s" % (e)
	
	try:
		answer = ''
		one_line = ''
		
		# send the command and retrieve the answer sent by the modem
		ser.write(command)
		ser.flush()
		
		# read on serial until empty line
		while True:
			one_line = ser.readline()
			if len(one_line) == 0:
				break
			answer+=one_line
		ser.close()

	except Exception as e:
		ser.close()
		return False, "Exception sending/reading serial command to the modem : %s" % (e)

	return True, answer

def checkModemReadyForATCommands(verbose = False):
	""" Send a basic AT command to the modem in order to validate that the modem is ready to exchange some AT commands, return tuple (status, message)
	Note: As it directly opens modem serial port, this function cannot be called when ofono is running... 
	"""

	if verbose:
		print ""
		print "-Check modem ready for AT command"
	
	# send basic AT command that retrieves modem serial number
	# we don't care about the returned serial number itself; 'OK' in the answer is good enough to determine that it is ready for some AT commands
	serial_command_status, modem_answer = sendSerialCommandToModem("AT+CGMR\r")

	status = False
	message = ""
	
	if serial_command_status:
		if len(modem_answer) == 0 or modem_answer.find('OK') == -1:
			message = "Modem is frozen or answered wrongly"
		else:
			status = True
			message = "Modem is ready for AT commands"	
	else:
		message = modem_answer
	
	if verbose:
		print message	
	
	return status, message

def loadModemFactoryConfiguration(verbose = False):
	""" Set modem configuration to "Factory-Defined Configuration".
	It sets the configuration parameters to default values specified by manufacturer.
	Both the factory profile base section and the extended section are considered (full factory profile).
	Note: As it directly opens modem serial port, this function cannot be called when ofono is running... 
	"""

	if verbose:
		print ""
		print "-Load modem factory-defined configuration"
		
	# Factory defined configuration is set by sending a specific AT command
	# increase read timeout as this command is longer to execute
	serial_command_status, modem_answer = sendSerialCommandToModem("AT&F1\r", 2)
	
	status = False
	message = ""

	if serial_command_status:
		if len(modem_answer) == 0:
			message = "No answer from modem"
		elif modem_answer.find('OK') == -1:
			message = "Bad answer from modem"
		else:
			status = True
			message = "Factory configuration applied"
	else:
		message = modem_answer
	
	if verbose:
		print message	
	
	return status, message

def rebootCurrentModem():
	"""look for USB modem currently mounted on the system and reboot it
	"""

	# look for a compatible modem on the system and reboot-it
	mounted_modems_count, mounted_modems_message, mounted_modems_list = listUSBModems(False) 
	if mounted_modems_count == 1:
		if mounted_modems_list[0].Location == USBModemLocation.Auto:
			return rebootAutoModem()
		elif mounted_modems_list[0].Location == USBModemLocation.Internal:
			return rebootInternalModem()

		return rebootExternalModem()

	print ""
	print "-Reboot Internal or USB modem"
	print "Error while detecting modem to reboot: %s" % (mounted_modems_message)
	
	return False

def rebootInternalModem():
	"""reboot the internal modem by activating a GPIO specific to the internal modem.
	"""
	print ""
	print "-Reboot Internal USB modem"
	
	internal_modem_reset_path = "/sys/class/leds/Reset_RF/brightness"
	try:
		# 0
		with open(internal_modem_reset_path, 'w') as internal_modem_reset_file:
			internal_modem_reset_file.write('0')
		# wait a bit
		time.sleep(0.5)
		# 255
		with open(internal_modem_reset_path, 'w') as internal_modem_reset_file:
			 internal_modem_reset_file.write('255')
		
		# wait a bit to let the modem reboot
		time.sleep(10)
		
		return True, "Reboot is done"
	except Exception as e:
		pass
	return False, "Exception while rebooting the internal modem:%s" % (e)

def rebootExternalModem():
	"""reboot the external modem by switching off/on the dedicated port on USB switch
	"""
	
	print ""
	print "-Reboot External USB modem"

	modem_library_name = 'libModem.so'
	try:
		# External modem reboot is done by using a dedicated C library
		modem_lib = ctypes.CDLL(modem_library_name)
		modem_lib.ResetExternalModem()

		# wait a bit to let the modem reboot
		time.sleep(10)
		
		return True, "Reboot is done"
	except Exception as e:
		pass
	return False, "Exception while rebooting external modem:%s" % (e)

def rebootAutoModem():
	"""Check if modem is connected to Internal/External USB and restart accordingly"""

	mounted_modems_count, mounted_modems_message, mounted_modems_list = listUSBModems(False)
	product_id = mounted_modems_list[0].VidPid.split(':')[1]

	product_id_files = glob.glob('/sys/bus/usb/devices/*/idProduct')
	for product_id_file in product_id_files:
		try:
			with open(product_id_file, 'r') as f:
				output = f.read()
			if output and output.strip() == product_id:
				# Detection for known internal bus on SC4200c
				if product_id_file.lstrip('/sys/bus/usb/devices/')[0:5] == '2-1.1':
					return rebootInternalModem()
				else:
					# Modem to check not on internal bus
					break
		except Exception as e:
			# Exception in case usb-driver or sysfs-interface is broken
			# Since this would be a really bad emergency, we would try to
			# reboot the modem with both procedures.

			print ""
			print "-Reboot Internal or External USB modem"
			print "Error while detecting way to reboot modem: %s" % (e)
			print "Triggering both ways to reboot modem"

			status_internal = rebootInternalModem()
			status_external = rebootExternalModem()

			if status_internal[0] is True or status_external[0] is True:
				return True, "Reboot is done"

			return False, "Failed on both modem-reboot procedures"

	return rebootExternalModem()

def restartConnman():
	return lib_system.restartService("connman.service")

def startOfonod():
	return lib_system.startService("ofono.service")

def stopOfonod():
	return lib_system.stopService("ofono.service")

def enableOfonod():
	return lib_system.enableService("ofono.service")

def disableOfonod():
	return lib_system.disableService("ofono.service")

def startCellularDataSupervisor():
	return lib_system.startService("cellular_data_supervisor.timer")

def stopCellularDataSupervisor():
	return lib_system.stopService("cellular_data_supervisor.timer")

def enableCellularDataSupervisor():
	return lib_system.enableService("cellular_data_supervisor.timer")

def disableCellularDataSupervisor():
	return lib_system.disableService("cellular_data_supervisor.timer")

def __getModemsFromDBus(bus):
	"""retrieve modems on DBus.
	"""
	modems = None
	message = ""
	try:
		manager = dbus.Interface(bus.get_object('org.ofono', '/'), 'org.ofono.Manager')
		modems = manager.GetModems()
		number = len(modems)
		if number == 0:
			modems = None
			message = "No modem found on DBus"
		else:
			message = "%d modem(s) found on DBus" % (number)
	except dbus.DBusException as e:
		modems = None
		message = "DBus exception while getting modems on DBus:%s" % (e)
	except Exception as e:
		modems = None
		message = "Generic exception while getting modems on DBus:%s" % (e)		
	return modems, message

def __retryFunction(function, bus, verbose, attempts, delay):
	""" call repeatedly a function given in parameter. A delay is applied between each call.
	It returns when the result of the called function is True or when the maximum of attempts is reached 
	""" 
	for attempt in range(0,attempts):
		if attempt > 0:
			time.sleep(delay)
			if verbose:
				print ""
				print "-Attempt #%d->" % (attempt+1)
		if bus is None:	# this function does not require any bus 
			status, message = function(verbose)
		else:
			status, message = function(bus, verbose)
		if status:
			break	#success, no need to retry anymore			
	return status, message

def isModemOnDBus(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__isModemOnDBus, bus, verbose, attempts, delay)

def __isModemOnDBus(bus, verbose):
	""" Check if the modem is visible on ofono DBus
	"""
	if verbose:
		print ""
		print "-Check for presence of modem on DBus"
	
	status = False
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		status = True
	if verbose:
		print message
	return status, message
	
def isSimPresent(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__isSimPresent, bus, verbose, attempts, delay)
	
def __isSimPresent(bus, verbose):
	""" retrieve Sim status (present or not)
	"""
	if verbose:
		print ""
		print "-Check for presence of Sim card"

	present = False
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		# we suppose there is only one modem connected	 
		modem_path = modems[0][0]
		
		present = False
		try:		
			sim_manager = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.SimManager')
			present = __dbus2py(sim_manager.GetProperties()['Present'])
			if present:
				message = "Sim is present"
			else:
				message = "Sim is absent"
			
		except dbus.DBusException as e:
			message = "DBus exception while checking presence of Sim card:%s" % (e)
		except KeyError as e:
			message = "KeyError exception: %s not present on Dbus" % (e)
		except Exception as e:
			message = "Generic exception while checking presence of Sim card:%s" % (e) 

	if verbose:
		print message
	return present, message

class PinStatus(Enum):
	Unknown = 0
	PinIsRequired = 1
	PukIsRequired = 2
	PinIsValidOrNotRequired = 3 
	 
def getPinStatus(bus, verbose=False):
	""" retrieve Pin status (returns PinStatus enum)
	"""	

	if verbose:
		print ""
		print "-Get Pin status"

	status = PinStatus.Unknown
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		# we suppose there is only one modem connected	 
		modem_path = modems[0][0]
	
		try:		
			sim_manager = dbus.Interface(bus.get_object('org.ofono', modem_path), 'org.ofono.SimManager')
			sim_pin_required = __dbus2py(sim_manager.GetProperties()['PinRequired'])
			if sim_pin_required == "none":
				message = "Pin is valid or not required"
				status = PinStatus.PinIsValidOrNotRequired 
			elif sim_pin_required == "puk":
				message = "A Puk is required"
				status = PinStatus.PukIsRequired
			else:
				message = "A Pin is required" 
				status = PinStatus.PinIsRequired
		except dbus.DBusException as e:
			message = "DBus exception while getting Pin status:%s" % (e) 
		except KeyError as e:
			message = "KeyError exception: %s not present on Dbus" % (e)
		except Exception as e:
			message = "Generic exception while getting Pin status:%s" % (e) 

	if verbose:
		print message
	return status, message

class DisablePinAnswer(Enum):
	Unknown = 0
	Success = 1
	WrongPin = 2

def disablePin(bus, pin, verbose=False):
	"""Enter a Pin and disable it. Sim must be locked by a Pin before. Exception otherwise
	"""
		
	if verbose:
		print ""
		print "-Enter and disable Pin using %s" % (pin)
	
	status = DisablePinAnswer.Unknown
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		modem_path = modems[0][0]
	
		try:		
			sim_manager = dbus.Interface(bus.get_object('org.ofono', modem_path), 'org.ofono.SimManager')
			sim_manager.EnterPin("pin", pin)
			sim_manager.UnlockPin("pin", pin)
			message = "Pin entered and disabled"
			status = DisablePinAnswer.Success
		except dbus.DBusException as e:
			#In case of wrong pin e is: "org.ofono.Error.Failed: Operation failed"
			#In case of no pin required e is: "org.ofono.Error.InvalidFormat: Argument format is not recognized"
			if str(e) == "org.ofono.Error.Failed: Operation failed":
				status = DisablePinAnswer.WrongPin
				message = "Wrong Pin"
			else:
				message = "DBus exception while entering/disabling pin:%s" % (e)
		except Exception as e:
			message = "Generic exception while entering/disabling pin:%s" % (e)

	if verbose:
		print message
	return status, message

def getServiceProviderName(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__getServiceProviderName, bus, verbose, attempts, delay)

def __getServiceProviderName(bus, verbose):
	""" retrieve Service Provicer Name
	"""

	if verbose:
		print ""
		print "-Get Service Provider Name"

	service_provider_name = ""

	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		# we suppose there is only one modem connected
		modem_path = modems[0][0]

		try:
			sim_manager = dbus.Interface(bus.get_object('org.ofono', modem_path), 'org.ofono.SimManager')
			service_provider_name = __dbus2py(sim_manager.GetProperties()['ServiceProviderName'])
			message = "Service Provider Name is \'%s\'" % (service_provider_name)
		except dbus.DBusException as e:
			message = "DBus exception while getting Service Provider Name:%s" % (e)
		except KeyError as e:
			"""Set service_provider_name to 'Unknown' if we can not get the real value from the SimManager
			interface. So far this has only been observed on Verizon-LTE and will only happen if GPRS is not
			registered in the classical way.
			When extending this function, keep in mind that this exception branch captures all dict-KeyErrors.
			"""
			service_provider_name = 'Unknown'
			message = "Service Provider Name is \'Unknown\' (most likely Verizon)"
		except Exception as e:
			message = "Generic exception while getting Service Provider Name:%s" % (e)

	if verbose:
		print message

	return service_provider_name, message

def enumerateModems(bus):
	"""Print all information accessible from ofono DBus regarding modems and their Sim
	"""

	print ""
	print "-Enumerate modems"

	modems, message = __getModemsFromDBus(bus)	
	if modems == None:
		print message
		return	 

	for path, properties in modems:
		print("[ %s ]" % (path))
	
		for key in properties.keys():
			if key in ["Interfaces", "Features"]:
				val = ""
				for i in properties[key]:
					val += i + " "
			else:
				val = properties[key]
			print("    %s = %s" % (key, val))
	
		for interface in properties["Interfaces"]:
			# Skip requesting of properties for (slow) Call* interfaces
			if 'Call' in interface:
				continue

			object = dbus.Interface(bus.get_object('org.ofono', path), interface)
	
			print("    [ %s ]" % (interface))
	
			try:
				properties = object.GetProperties()
				for key in properties.keys():
					if key in ["Calls",	"MultipartyCalls", "EmergencyNumbers", "SubscriberNumbers", "PreferredLanguages", "PrimaryContexts", "LockedPins", "Features", "AvailableTechnologies"]:
						val = ""
						for i in properties[key]:
							val += i + " "
					elif key in ["ServiceNumbers"]:
						val = ""
						for i in properties[key]:
							val += "[" + i + "] = '"
							val += properties[key][i] + "' "
					elif key in ["MobileNetworkCodeLength",	"VoicemailMessageCount", "MicrophoneVolume", "SpeakerVolume", "Strength", "DataStrength", "BatteryChargeLevel"]:
						val = int(properties[key])
					elif key in ["MainMenu"]:
						val = ", ".join([ text + " (" + str(int(icon)) + ")" for text, icon in properties[key] ])
					elif key in ["Retries"]:
						val = ""
						for i in properties[key]:
							val +=  "[" + i + " = "
							val += str(int(properties[key][i])) + "] "
					elif key in ["Settings"]:
						val = "{"
						for i in properties[key].keys():
							val += " " + i + "="
							if i in ["DomainNameServers"]:
								for n in properties[key][i]:
									val += n + ","
							else:
								val += properties[key][i]
						val += " }"
					else:
						val = properties[key]
					print("        %s = %s" % (key, val))
			except:
				continue
		print('')

def isCellularNetworkRegistered(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__isCellularNetworkRegistered, bus, verbose, attempts, delay)

def __isCellularNetworkRegistered(bus, verbose):
	""" retrieve cellular network registration status (registered or not)
	"""
	
	if verbose:
		print ""
		print "-Check for cellular network registration"
		
	status = False
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		modem_path = modems[0][0]
		
		try:		
			# retrieve org.ofono.NetworkRegistration.Status property: The current network registration status of the modem
			# doc: The possible values are:
			#	"unregistered"  Not registered to any network
			#	"registered"    Registered to home network
			#	"searching"     Not registered, but searching
			#	"denied"        Registration has been denied
			#	"unknown"       Status is unknown
			#	"roaming"       Registered, but roaming
			network_registration_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.NetworkRegistration')
			network_registration_status = __dbus2py(network_registration_interface.GetProperties()['Status'])
			message = "Cellular network is %s" % (network_registration_status)
			if network_registration_status == "registered" or network_registration_status == "roaming":
				status = True
			else:
				"""If NetworkRegistration interface is not set to registered/roaming it is still possible that we are able to connect
				via plain LTE. To verify we are checking the ConnectionsManagers bearer (needs to be 'lte') and the corresponding
				'attached' value (needs to be 'true'/'1').
				So far, this kind of LTE behavior was only seen on provider 'Verizon'.
				"""
				connection_manager_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.ConnectionManager')
				connection_manager_bearer = __dbus2py(connection_manager_interface.GetProperties()['Bearer'])
				connection_manager_attached = __dbus2py(connection_manager_interface.GetProperties()['Attached'])
				if connection_manager_bearer.lower() == 'lte' and connection_manager_attached:
					message = "Cellular network is registered via native LTE"
					status = True
		except dbus.DBusException as e:
			message = "DBus exception while checking for network resgistration:%s" % (e)
		except KeyError as e:
			message = "KeyError exception: %s not present on Dbus" % (e)
		except Exception as e:
			message = "Generic exception while checking for network resgistration:%s" % (e) 

	if verbose:
		print message
	return status, message

def isDataNetworkRegistered(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__isDataNetworkRegistered, bus, verbose, attempts, delay)

def __isDataNetworkRegistered(bus, verbose):
	""" retrieve data network registration status (registered or not)
	"""
	
	if verbose:
		print ""
		print "-Check for data network registration"
	
	status = False
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		modem_path = modems[0][0]
		
		try:
			# retrieve org.ofono.ConnectionManager.Attached property: Contains whether the Packet Radio Service is attached
			# doc: "If this value changes to false, the user can assume that all contexts have been deactivated"
			# doc: "If the modem is detached, certain features will not be available, e.g. receiving SMS over packet radio or network initiated PDP activation."
			connection_manager_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.ConnectionManager')
			gprs_attached = __dbus2py(connection_manager_interface.GetProperties()['Attached'])
			if gprs_attached:
				status = True
				message = "Attached to GPRS/3G/4G network"
			else:
				message = "Not attached to any GPRS/3G/4G network"
		except dbus.DBusException as e:
			message = "DBus exception while checking for data network resgistration:%s" % (e)
		except KeyError as e:
			message = "KeyError exception: %s not present on Dbus" % (e)
		except Exception as e:
			message = "Generic exception while checking for data network resgistration:%s" % (e)

	if verbose:
		print message
	return status, message

def isRoamingAllowed(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__isRoamingAllowed, bus, verbose, attempts, delay)

def __isRoamingAllowed(bus, verbose):
	""" retrieve roaming allowed status (roaming allowed or not)
	"""
	
	if verbose:
		print ""
		print "-Check for roaming allowed"
	
	status = False
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		modem_path = modems[0][0]
		
		try:
			# retrieve org.ofono.ConnectionManager.RoamingAllowed property
			connection_manager_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.ConnectionManager')
			roaming_allowed = __dbus2py(connection_manager_interface.GetProperties()['RoamingAllowed'])
			if roaming_allowed:
				status = True
				message = "Roaming is allowed"
			else:
				message = "Roaming is not allowed"
		except dbus.DBusException as e:
			message = "DBus exception while reading Roaming allowed status:%s" % (e)
		except KeyError as e:
			message = "KeyError exception: %s not present on Dbus" % (e)
		except Exception as e:
			message = "Generic exception while reading Roaming allowed status:%s" % (e) 

	if verbose:
		print message
	return status, message


def setRoamingAllowed(bus, roamingAllowed=True):
	"""
	In ofono DBUS: "org.ofono.NetworkRegistration.Status == roaming" as soon as the SIM is not in the country of the service provider.
	In this case the modem is attached to a local network for voice only. 
	It is necessary to set "org.ofono.ConnectionManager.RoamingAllowed to 1" to enable DATA roaming (GPRS, 3G, 4G)... this is the goal of this function
	"""
	
	print ""
	if roamingAllowed:
		print "-Enable data roaming"
	else:
		print "-Disable data roaming"
		
	status = False
		
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		modem_path = modems[0][0]
		
		try:
			# retrieve org.ofono.ConnectionManager.RoamingAllowed property
			connection_manager_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.ConnectionManager')
			connection_manager_interface.SetProperty("RoamingAllowed", dbus.Boolean(roamingAllowed))
			status = True
		except dbus.DBusException as e:
			message = "DBus exception while setting RoamingAllowed:%s" % (e)
		except Exception as e:
			message = "Generic exception while setting RoamingAllowed:%s" % (e)
	return status, message 

def getSignalStrength(bus, verbose=False):
	"""retrieve the signal strength of the connected modem (0-20-40-60-80-100)
	"""
	if verbose:
		print ""
		print "-Get signal strength"
	
	strength = 0
	# it is mandatory to be registered to a network to have access to signal strength propertie in DBus
	network_registered, message = isCellularNetworkRegistered(bus)
	
	if network_registered:
		modems, message = __getModemsFromDBus(bus)
		if modems != None:
			modem_path = modems[0][0]
			
			try:
				# retrieve the interface containing the signal strength (org.ofono.NetworkRegistration)
				network_registration_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.NetworkRegistration')
				network_registration_properties = network_registration_interface.GetProperties()
				length = len(network_registration_properties)
				if length > 5 and (not 'Strength' in network_registration_properties):
					#print "Start of ugly patch to retrieve the missing signal strength propertie on DBus"

					# This is a ugly patch to solve bug FCON-1235:
					# Sometimes org.ofono.NetworkRegistration.Strength propertie is not available but all other properties from org.ofono.NetworkRegistration are available
					# normally org.ofono.NetworkRegistration interface contains 9 properties, only 8 when Strength is missing...
					# The fact to list properties from org.ofono.CallBarring and org.ofono.CallSettings make org.ofono.NetworkRegistration.Strength available again on DBus...
		
					#Most of the time this bug occurs right after modem configuration or ofono.service restart
					#let some long seconds before reading many properties on DBus that put ofono in failure...
					#the happyness of using open source code...
					time.sleep(10)
		
					call_barring_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.CallBarring')
					call_barring_properties = call_barring_interface.GetProperties()
		
					call_settings_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.CallSettings')
					call_settings_properties = call_settings_interface.GetProperties()
		
					# ask again for these properties
					network_registration_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.NetworkRegistration')
					network_registration_properties = network_registration_interface.GetProperties()
		
					#print "End of ugly patch to retrieve the missing signal strength value on DBus"
				strength = __dbus2py(network_registration_properties['Strength'])
				message = "Signal strength is %d%%" % (strength)
			except dbus.DBusException as e:
				message = "DBus exception while getting signal strength:%s" % (e)
			except KeyError as e:
				message = "Impossible to get signal strength right now"			
			except Exception as e:
				message = "Generic exception while getting signal strength:%s" % (e) 
	if verbose:
		print message			
	return strength, message		

def clearInternetContext(bus):
	""" Clear existing internet context in Sim.
	"""
	
	print ""
	print "-Clear existing internet context"
	
	modems, message = __getModemsFromDBus(bus)
	if modems == None:
		print message
		return	 

	try:
		for path, properties in modems:
			if "org.ofono.ConnectionManager" not in properties["Interfaces"]:
				continue
		
			connman = dbus.Interface(bus.get_object('org.ofono', path), 'org.ofono.ConnectionManager')
			contexts = connman.GetContexts()
			
			# remove existings contexts
			for path, properties in contexts:
				connman.RemoveContext(path)
				print("Removed: [ %s ]" % (path))
		return 

	except dbus.DBusException as e:
		message = "DBus exception while clearing internet contexts:%s" % (e)
	except Exception as e:
		message = "Generic exception while clearing internet contexts:%s" % (e) 
				
def setInternetContext(bus, apn, user_name, password):
	""" Configure APN settings by creating an "Internet context" (stored in Sim)
	"""
	
	print ""
	if len(apn) > 0:
		print "-Configure internet context using %s" % (apn)
	else:
		print "-Configure internet context without any apn"
	
	modems, message = __getModemsFromDBus(bus)
	if modems == None:
		print message
		return	 

	try:
		for path, properties in modems:
			if "org.ofono.ConnectionManager" not in properties["Interfaces"]:
				continue
		
			connman = dbus.Interface(bus.get_object('org.ofono', path), 'org.ofono.ConnectionManager')
			contexts = connman.GetContexts()
	
			path = ""
			for i, properties in contexts:
				if properties["Type"] == "internet":
					path = i
					break
		
			if path == "":
				path = connman.AddContext("internet")
				print("Created new context %s" % (path))
			else:
				print("Found context %s" % (path))
		
			context = dbus.Interface(bus.get_object('org.ofono', path),	'org.ofono.ConnectionContext')
		
			if len(apn) > 0:
				context.SetProperty("AccessPointName", apn)
				print("Setting apn to %s" % (apn))
							
			if len(user_name) > 0:
				context.SetProperty("Username", user_name)
				print("Setting username to %s" % (user_name))
		
			if len(password) > 0:
				context.SetProperty("Password", password)
				print("Setting password to %s" % (password))
			
			#Necessary to wait a bit after context creation
			time.sleep(3)
	except dbus.DBusException as e:
		message = "DBus exception while setting internet contexts:%s" % (e)
	except Exception as e:
		message = "Generic exception while setting internet contexts:%s" % (e) 

def isInternetContextActive(bus, verbose=False, attempts=1, delay=5):
	return __retryFunction(__isInternetContextActive, bus, verbose, attempts, delay)

def __isInternetContextActive(bus, verbose):
	""" retrieve Internet context status in Ofono (active or not). An active Internet context means ppp0 is mounted
	"""
	
	if verbose:
		print ""
		print "-Check for Internet context"
	
	status = False
	
	modems, message = __getModemsFromDBus(bus)
	if modems != None:
		modem_path = modems[0][0]
	
		try:
			connection_manager_interface = dbus.Interface(bus.get_object('org.ofono', modem_path),'org.ofono.ConnectionManager')
			
			# retrieve internet context
			contexts = connection_manager_interface.GetContexts()
			# use first context
			if len(contexts) == 0:
				message = "No Internet context found"
			
			# ofono.ConnectionContext contains information about ppp connection
			the_context = contexts[0][0]
			connection_context = dbus.Interface(bus.get_object('org.ofono', the_context),'org.ofono.ConnectionContext')
			status = __dbus2py(connection_context.GetProperties()['Active'])
			if status:
				message = "Internet context is active"
			else:
				message = "Internet context is not active"
			
		except dbus.DBusException as e:
			message = "DBus exception while checking Internet context status:%s" % (e)
		except KeyError as e:
			message = "KeyError exception: %s not present on Dbus" % (e) 
		except Exception as e:
			message = "Generic exception while checking Internet context status:%s" % (e) 
	
	if verbose:
		print message
	return status, message

def contactServer(verbose=False, attempts=1, delay=5):
	return __retryFunction(__contactServer, None, verbose, attempts, delay)

def __contactServer(verbose=False):
	"""try to establish a connection to a server
	Two reference servers are used; return True if a least one server is reachable
	"""
	status = False
	servers_urls = [ 'storefsngeneral.blob.core.windows.net', 'pool.ntp.org' ]
	count = 0
	message = ""
	for server_url in servers_urls:
		if verbose:
			print ""
			print "-Contact %s" % (server_url)

		if lib_network.contact_server(interface_name=CELLULAR_INTERFACE_NAME, server_urls=[server_url]):
			status = True
			loop_msg = "Contact to %s successful" % (server_url)
		else:
			loop_msg = "Impossible to contact %s"  % (server_url)

		if verbose:
			print loop_msg
		if count > 0:
			message += ", "
		message += loop_msg

		# the first successful contact exits the loop
		if status == True:
			break
		else:
			count += 1

	return status, message

def getCellularServiceInConnman(bus, verbose=False, attempts=3, delay=5):
	return __retryFunction(__getCellularServiceInConnman, bus, verbose, attempts, delay)
	
def __getCellularServiceInConnman(bus, verbose):
	""" Wait until a cellular service is listed by Connman
	"""
	
	if verbose:
		print ""
		print "-Wait for cellular service creation by ofono, listed in connman..."
	status = False
	try:
		manager = dbus.Interface(bus.get_object("net.connman", "/"), "net.connman.Manager")
		services = manager.GetServices()
		for path, properties in services:
			if path.find("cellular_") != -1:
				status = True
				message = path								
				#the service is listed but not really ready to be connected... wait a bit for it
				#this timing differs from one provider to another...
				time.sleep(5)
				break
		if not status:
			message = "No cellular service seen by Connman"
	except dbus.DBusException as e:
		message = "DBus exception while waiting for cellular service in Connman:%s" % (e)
	except Exception as e:
		message = "Generic exception while waiting for cellular service in Connman:%s" % (e)
	if verbose:
		print message
	return status, message

def connectToCellularServiceInConnman(bus, cellular_service_path, verbose=False, attempts=2, delay=5):
	"""Connect connman to a cellular service, with retries
	""" 
	for attempt in range(0,attempts):
		if attempt > 0:
			time.sleep(delay)
			if verbose:
				print ""
				print "-Attempt #%d->" % (attempt+1)
		status, message = __connectToCellularServiceInConnman(bus, cellular_service_path, verbose)
		if status:
			break	#success, no need to retry anymore			
	return status, message

def __connectToCellularServiceInConnman(bus, cellular_service_path, verbose):
	"""Connect connman to a cellular service.
	"""
	
	if verbose:
		print ""
		print "-Connect to cellular service \"%s\"..." % (cellular_service_path)
	status = False
	try:
		service = dbus.Interface(bus.get_object("net.connman", cellular_service_path), "net.connman.Service")
		service.Connect(timeout=10000)	#ask for connection, use long timeout
		service.SetProperty("AutoConnect", True)	#mandatory: this cellular service will automatically be connected at next startup
		status = True
		message = "Successful connection"
	except dbus.DBusException as e:
		message = "DBus exception while connecting to cellular service in Connman:%s" % (e)
	except Exception as e:
		message = "Generic exception while connecting to cellular service in Connman:%s" % (e)
	if verbose:
		print message
	return status, message

def isOfonodRunning(verbose=False):
	"""check if ofono daemon is running, return tuple (status, message)
	"""
	return lib_system.isProcessRunning("ofonod", verbose)
