#!/usr/bin/python
# -*- coding: utf-8 -*-
# Project:     wifi-ltsp
# Module:     escanea.py
# Purpose:     Busca el canal libre más adecuado para la wifi
# Language:    Python 2.5
# Date:        03-Feb-2011.
# Ver:        07-Feb-2011.
# Author:    Francisco Mora Sánchez
# Copyright:   2011 - Francisco Mora Sánchez   <adminies.maestrojuancalero@edu.juntaextremadura.net>
#
# wifi-ltsp is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Script2 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with wifi-ltsp. If not, see <http://www.gnu.org/licenses/>.


"""Módulo auxiliar para obtener información a través
del comando iwlist, perteneciente a las herramientas
wireless-tools

Francisco Mora Sánchez
IES Maestro Juan Calero
adminies.maestrojuancalero@edu.juntaextremadura.net


"""

import os
import re
import locale
import time
from subprocess import Popen, STDOUT, PIPE, call

# Expresiones Regulares.
_re_mode = (re.I | re.M | re.S)
essid_pattern = re.compile('.*ESSID:"?(.*?)"?\s*\n', _re_mode)
ap_mac_pattern = re.compile('.*Address: (.*?)\n', _re_mode)
channel_pattern = re.compile('.*Channel:?=? ?(\d\d?)', _re_mode)
strength_pattern = re.compile('.*Quality:?=? ?(\d+)\s*/?\s*(\d*)', _re_mode)
altstrength_pattern = re.compile('.*Signal level:?=? ?(\d+)\s*/?\s*(\d*)', _re_mode)
signaldbm_pattern = re.compile('.*Signal level:?=? ?(-\d\d*)', _re_mode)
freq_pattern = re.compile('.*Frequency:(.*?)\n', _re_mode)

def to_unicode(x):
    """ Convierte una cadena a codificación utf-8. """
    # Si ésta es una cadena unicode, la codifica y la devuelve
    if not isinstance(x, basestring):
        return x
    if isinstance(x, unicode):
        return x.encode('utf-8')
    encoding = locale.getpreferredencoding()
    try:
        ret = x.decode(encoding).encode('utf-8')
    except UnicodeError:
        try:
            ret = x.decode('utf-8').encode('utf-8')
        except UnicodeError:
            try:
                ret = x.decode('latin-1').encode('utf-8')
            except UnicodeError:
                ret = x.decode('utf-8', 'replace').encode('utf-8')
            
    return ret

def EjecutaRegex(regex, cadena):
    """ ejecuta búsqueda de expresión regular en una cadena """
    m = regex.search(cadena)
    if m:
        return m.groups()[0]
    else:
        return None

def Ejecuta(comando, include_stderr=False, return_pipe=False,
        return_obj=False, return_retcode=True):
    """ Ejecuta un comando.

    Ejecuta el comando dado, retornando la salida del programa
    o un pipe para leer la salida.

    argumentos --
    comando - Comando a ejecutar
    include_std_err - Boleano, especifica si la salida de error debe
					ser incluida en el pipe.
    return_pipe - Boleano, especifica si el pipe del comando se
					devuelve. Si es False, todo lo que devolverá
					es la cadena de salida del comando.
    return_obj - Si True, Ejecuta devolverá el objeto Popen
					para el comando que se ha ejecutado.

    """
    if not isinstance(comando, list):
        comando = to_unicode(str(comando))
        comando = comando.split()
    if include_stderr:
        err = STDOUT
        fds = True
    else:
        err = None
        fds = False
    if return_obj:
        std_in = PIPE
    else:
        std_in = None
    
    # Debemos asegurarnos que los resultados del comando ejecutado
    # están en inglés, así ajustaremos un entorno temporal.
    tmpenv = os.environ.copy()
    tmpenv["LC_ALL"] = "C"
    tmpenv["LANG"] = "C"
    
    try:
        f = Popen(comando, shell=False, stdout=PIPE, stdin=std_in, stderr=err,
                  close_fds=fds, cwd='/', env=tmpenv)
    except OSError, e:
        print "Fallo ejecuando comando %s : %s" % (str(comando), str(e))
        return ""
        
    if return_obj:
        return f
    if return_pipe:
        return f.stdout
    else:
        return f.communicate()[0]

def FrecuenciaACanal(frecuencia):
	""" Transforma una frecuencia a canal.
	
		Nota: Esta función es una búsqueda en diccionario, por lo que
		la frecuencia debe estar en el diccionario para que pueda
		devolverse un canal válido.
		
        Parámetros:
        frecuencia -- cadena conteniendo la frecuencia
        
        Devuelve:
        El número de canal, o None si no se encuentra.
	"""
	ret = None
	freq_dict = {'2.412 GHz': 1, '2.417 GHz': 2, '2.422 GHz': 3,
		'2.427 GHz': 4, '2.432 GHz': 5, '2.437 GHz': 6,
		'2.442 GHz': 7, '2.447 GHz': 8, '2.452 GHz': 9,
		'2.457 GHz': 10, '2.462 GHz': 11, '2.467 GHz': 12,
		'2.472 GHz': 13, '2.484 GHz': 14 }
	try:
		ret = freq_dict[frecuencia]
	except KeyError:
		print "No se puede determinar el canal para la frecuencia: " + str(frecuencia)
	return ret

def get_link_quality(red):
	""" Obtiene la calidad del enlace desde la salida iwlist.
	"""
	try:
		[(strength, max_strength)] = strength_pattern.findall(red)
	except ValueError:
		(strength, max_strength) = (None, None)
	if strength in ['', None]:
		try:
			[(strength, max_strength)] = altstrength_pattern.findall(red)
		except ValueError:
			# Si el patrón no encuentra coincidencias
			# retornamos 101
			return 101
	if strength not in ['', None] and max_strength:
		#print "strength,max",strength,max_strength
		return (100 * int(strength) // int(max_strength))
	elif strength not in ["", None]:
		#print "strength,max",strength,max_strength
		return int(strength)
	else:
		#print "strength,max",strength,max_strength
		return None
    
def ParseAccessPoint(red):
	""" Examina una red wifi desde la salida de iwlist.
		Parámetros:
		red -- cadena que contiene la identificación de la red.
		Devuelve:
		Un diccionario que contiene las propiedades de la red wifi
		examinada.
	"""
	ap = {}
	ap['essid'] = EjecutaRegex(essid_pattern, red)
	try:
		ap['essid'] = to_unicode(ap['essid'])
	except (UnicodeDecodeError, UnicodeEncodeError):
		print 'Problema Unicode con el essid de la red actual, ignorando!!'
		return None
	if ap['essid'] in ['Hidden', '<hidden>', "", None]:
		print 'hidden'
		ap['oculta'] = True
		ap['essid'] = "<hidden>"
	else:
		ap['oculta'] = False
	# Canal - Para interfaces que no tienen un número de canal,
	# convertir la frecuencia.
	ap['canal'] = EjecutaRegex(channel_pattern, red)
	if ap['canal'] == None:
		freq = EjecutaRegex(freq_pattern, red)
		ap['canal'] = FrecuenciaACanal(freq)
	# BSSID
	ap['bssid'] = EjecutaRegex(ap_mac_pattern, red)
	# Calidad del enlace
	# Ajusta strength a -1 si no se encuentra calidad
	ap['calidad'] = get_link_quality(red)
	if ap['calidad'] is None:
		ap['calidad'] = -1
	# Signal Strength (only used if user doesn't want link
	# quality displayed or it isn't found)
	if EjecutaRegex(signaldbm_pattern, red):
		ap['intensidad'] = EjecutaRegex(signaldbm_pattern, red)
	return ap

def getWirelessInterfaces():
    """ Extract wireless device names from /proc/net/wireless.

        Returns empty list if no devices are present.

        >>> getWirelessInterfaces()
        ['eth1', 'wifi0']

    """
    device = re.compile('[a-z]{2,}[0-9]*:')
    ifnames = []

    fp = open('/proc/net/wireless', 'r')
    for line in fp:
        try:
            # append matching pattern, without the trailing colon
            ifnames.append(device.search(line).group()[:-1])
        except AttributeError:
            pass

    return ifnames

def ObtieneRedes(interface):
	""" Obtiene una lista de redes wifi disponibles. La usamos para 
		obtener via iwlist datos de las redes disponibles. De momento
		lo usamos para obtener los valores del calidad e intensidad,
		ya que la librería pythonwifi no parece dar los valores correctos
		de estos parámetros.
	
		Parámetros:
		interface -- Interfaz sobre la que escanear
		Devuelve:
		Diccionario cuyas claves son ls bssids y cada elemento
		es otro diccionario cuyas claves con las características
		recogidas por iwlist
	"""
    
    #Si hostapd está corriendo iwlist scan no funciona:
	Popen(["invoke-rc.d","hostapd","stop"]).wait()
	time.sleep(2.0)
	Popen(["ifconfig",interface,"up"]).wait()
    
	cmd = 'iwlist ' + interface + ' scan'
	resultado = Ejecuta(cmd)
	# Divide las redes, utilizando Cell como punto de división
	# de esta forma podemos mirar una red cada vez.
	# Los espacios alrededor de '   Cell ' son para evitar el caso
	# de que alguien tenga un essid llamado Cell...
	redes = resultado.split( '   Cell ' )
	# An array for the access points
	access_points = []
	access_points = {}
	for red in redes:
		# Solo usa secciones donde haya un ESSID.
		if 'ESSID:' in red:
			# Añadir la red a la lista de redes
			entry = ParseAccessPoint(red)
			if entry is not None:
				# Normalmente solo tenemos bssids duplicados con redes
				# ocultas.  Solo nos fijamos en el essid real, para
				# que esté en la lista.
				if (entry['bssid'] not in access_points or not entry['oculta']):
					access_points[entry['bssid']] = entry
	return access_points

if __name__ == "__main__":
	datos_redes = ObtieneRedes('eth1')
	print datos_redes
