2. descripción general del sistema. el problema de la multilínea
Transcripción
2. descripción general del sistema. el problema de la multilínea
Capítulo 2: Descripción general del sistema. El problema de la multilínea 2. DESCRIPCIÓN GENERAL DEL SISTEMA. EL PROBLEMA DE LA MULTILÍNEA 2.1 INTRODUCCIÓN En este capítulo veremos la arquitectura hardware del sistema, las dificultades que surgieron cuando se planteó el desarrollo de una versión multilínea del sistema telefónico ya existente y las soluciones adoptadas. 2.2 ARQUITECTURA HARDWARE Cuando hay que plantearse la arquitectura hardware de un sistema basado en las tecnologías del habla hay que tener en cuenta varios aspectos importantes: la modularidad del sistema, el coste del sistema y el uso que vaya a tener. Hay dos posibilidades de tipo genérico: • Solución interfaz: consiste en equipar un terminal telefónico normal con una serie de interfaces, estándares si es posible, que permitan conectar al terminal dispositivos externos. Esta solución tiene unos costes bastante bajos y esta orientada a sistemas instalables en casa de los usuarios, los cuales suelen tener alguna discapacidad. Las interfaces del terminal deben permitir la conexión, por ejemplo, de un dispositivo de marcación controlado por voz, un sistema de salida audio, pantallas tipo Braille, micrófonos y altavoces especiales, etc. • Solución PC: utiliza un ordenador, tipo PC, complementado con las tarjetas que sea necesario, normalmente una interfaz de línea telefónica y una o varias tarjetas que soporten el reconocimiento y la síntesis de voz. Esta solución es la más adecuada para aquellos sistemas que van a ser utilizados por los usuarios a través de la línea telefónica. Pág. 2-1 Capítulo 2: Descripción general del sistema. El problema de la multilínea En nuestro caso se ha optado por la segunda alternativa. Sobre el PC recaen las tareas de control del sistema y las tarjetas que hay que añadir son las siguientes: • Tarjeta interfaz de línea telefónica (IFTEL): esta tarjeta conecta el sistema a la línea telefónica y se encarga de adaptar las señales entre la línea telefónica y la tarjeta de procesado digital de señal. También permite conectar dispositivos auxiliares al sistema, como auriculares, equipos de grabación de voz, etc. • Tarjeta de procesado digital de señal (VISHA): está equipada con un procesador de señal (DSP), un CODEC y memoria RAM. Realiza tareas de reconocimiento, reproducción y grabación de voz. 2.2.1 Interfaz de línea telefónica La principal función que realiza la interfaz de línea es realizar la conexión del sistema con la línea telefónica, realizando la conversión de 2 a 4 hilos y adaptando los niveles de señal a ambos lados de la interfaz. Además, y debido a las necesidades del sistema, realiza otras muchas funciones, entre las que se encuentran las siguientes: • • • • • • Adaptación de señales para conectar dispositivos externos auxiliares de entrada y de salida, como altavoces, micrófonos y auriculares. Control de la conexión/desconexión con la línea telefónica. Control del dispositivo externo para la grabación de la conversación sistema/usuario. Detección de llamadas. Detección de tonos multifrecuencia (DTMF). Detección de paso a falta (el usuario cuelga). Esta interfaz de línea se conecta a uno de los conectores de expansión de la placa principal del PC. La dirección base puede variarse a través de unos microinterruptores existentes en la propia tarjeta de la interfaz, como se puede ver en el Apéndice A Manual del usuario. 2.2.2 Placa VISHA Es una tarjeta diseñada para el procesamiento digital de la señal de voz y utiliza el DSP32C. Entre otras, implementa las siguientes funciones: • • Reconocimiento de voz, concretamente la segmentación y la obtención de los parámetros de análisis. Reproducción/grabación, a través de un codificador/decodificador de voz que utiliza codificación PCM. Pág. 2-2 Capítulo 2: Descripción general del sistema. El problema de la multilínea También se conecta a uno de los conectores de expansión de la placa principal del PC. Ocupa 16 posiciones consecutivas en el mapa de entrada/salida y la dirección base puede variarse a través de unos microinterruptores existentes en la propia tarjeta de la interfaz (ver Apéndice A Manual del usuario). 2.2.3 Dispositivos auxiliares Además de los elementos esenciales para el funcionamiento del sistema, es posible conectar otros dispositivos auxiliares que permiten aumentar su funcionalidad, como: • Un dispositivo externo de grabación: durante la fase de pruebas de una determinada aplicación se suelen realizar estudios sobre el comportamiento de los usuarios frente al sistema. Para ello es necesario grabar la conversación entre el usuario y el sistema. Esto puede realizarse gracias a una salida de la interfaz de línea que permite controlar un DAT (Digital Audio Tape) externo. Cuando se detecta una llamada se puede activar el DAT y se le proporciona la señal de audio con los niveles adecuados, grabándose de esta forma la conversación usuario-sistema. Al finalizar la llamada, se desactiva el DAT. • Micrófono y altavoz: el funcionamiento normal de las aplicaciones es a través de la línea telefónica. Sin embargo, puede ser interesante permitir el acceso a través del propio ordenador. Para ello es necesario disponer de un micrófono y de un altavoz, de forma que sea posible la comunicación oral con el sistema. Estos dispositivos se conectan a la tarjeta interfaz de línea, encargándose ésta de realizar la adaptación de las señales a los niveles requeridos. Pág. 2-3 Capítulo 2: Descripción general del sistema. El problema de la multilínea 2.3 EL PROBLEMA DE LA MULTILÍNEA Al abordar la conversión del sistema monolínea a multilínea surgieron las siguientes limitaciones que dificultaban la labor: Respecto al hardware: • Las tarjetas hardware utilizadas son monolínea (una tarjeta sólo es capaz de atender una línea), y cada línea necesita dos: una tarjeta interfaz telefónica (IFTEL) y una tarjeta de procesado digital de señal (VISHA). Para más información sobre ellas, consultar el Apéndice A Manual del Usuario. • Las tarjetas son para bus ISA, con lo que la situación se agrava al coexistir dos tipos de bus en los ordenadores personales actuales (ISA y PCI, normalmente 4 slots de cada tipo). Por tanto, si no se utiliza un expansor de bus, sólo será posible disponer de dos líneas por PC. • Dichas tarjetas no generan interrupciones, lo que obliga a consultarlas continuamente en espera de algún evento. En el sistema monolínea, al atender una sola línea, no planteaba problemas, pues el sistema podía quedarse en un bucle realizando consultas. En el sistema multilínea esto es inviable, pues tenemos otra línea que atender. Respecto al software: • Algunas instrucciones del lenguaje, sencillas en apariencia, ocultaban código de elevada complejidad, como era el caso de la instrucción Verificar_Cadena, que hacía uso del reconocimiento y de la reproducción. • Enlazando con el tercer punto anterior, la presencia de bucles de consulta al hardware era constante. Pág. 2-4 Capítulo 2: Descripción general del sistema. El problema de la multilínea 2.4 SOLUCIONES ADOPTADAS Respecto a las limitaciones hardware, poco se puede hacer si queremos seguir utilizando las tarjetas hardware que se utilizaban en el sistema monolínea. Otra posibilidad era utilizar otro tipo de hardware. La solución adoptada ha sido hacer el nuevo sistema multilínea compatible tanto con el hardware descrito (VISHA + IFTEL) como con las tarjetas DIALOGIC, que en una misma tarjeta integran la interfaz telefónica y parte del procesado de señal, y además, permiten atender varias líneas telefónicas con una sola tarjeta. Es en el aspecto software donde están las novedades. Concretamente: • • • • Se ha rediseñado completamente el compilador, con el objetivo de potenciar el lenguaje y disponer de más instrucciones, más sencillas y más flexibles. Las instrucciones complejas que existían en el sistema monolínea seguirán disponibles en el sistema multilínea, pero en forma de subrutinas de alto nivel. Como se puede deducir de los dos puntos anteriores, el objetivo es dejar la complejidad al lenguaje y simplificar los niveles inferiores, que es donde debemos lograr la funcionalidad mutilínea buscada. La ejecución de las aplicaciones está dirigida por eventos. Pronto veremos las razones. El resto del capítulo detallará la arquitectura software del sistema. 2.5 FILOSOFÍA MULTILÍNEA 2.5.1 Introducción El sistema multilínea está diseñado para ejecutarse en entorno Windows. En su desarrollo se ha utilizado el compilador Borland C++ v. 4.5 y se hace uso de la librería de clases orientada a objeto ObjectWindows v. 2.5, que facilita enormemente la programación en entorno Windows. También existe una versión EasyWin (una aplicación DOS que puede ejecutarse en una ventana Windows), que implementa la funcionalidad suficiente para interpretar aplicaciones. ObjectWindows nos proporciona una función un tanto especial, denominada IdleAction. Su funcionamiento es muy simple: se la llama automáticamente siempre que no haya mensajes esperando ser procesados en la cola de mensajes de Windows. Sobre la base de esta función se ha construido toda la arquitectura software del sistema. Esta función está disponible en otros compiladores, como en Visual C++ de Microsoft, por tanto la portabilidad o el cambio de compilador en el futuro no supondrán ningún problema. Pág. 2-5 Capítulo 2: Descripción general del sistema. El problema de la multilínea Nos interesa que las llamadas a esta función IdleAction sean tan frecuentes como sea posible, para ello: 2.5.2 • Conviene que Windows tenga pocos mensajes que procesar. Los mensajes se producen como resultado de acciones del usuario (movimiento del ratón, pulsación del teclado) o bien son generados por las aplicaciones. Dado que el sistema está diseñado para actuar de forma desatendida y ser la única aplicación que se ejecute, sólo tendremos mensajes generados por el propio sistema y por Windows. El sistema, si bien está diseñado para entorno Windows y es normal que estuviera dirigido por mensajes, no es así, pues está dirigido por eventos. Esto nos garantiza una baja tasa de mensajes a procesar. • Las acciones que deben realizarse en la función IdleAction deben consumir el menor tiempo que sea posible, pues es la única forma de garantizar que se la llamará muchas veces por segundo. Esto lo conseguimos extendiendo la filosofía idle al resto del programa, como veremos enseguida. Extensión del concepto idle La función IdleAction llamará a otra función, que llamaremos idle principal y tiene el siguiente aspecto: if (get_event (evento) != 0) { if (Realiza_Transicion (evento)) { Ejecuta (evento); idle(); } while (get_event_nulo(evento) != 0) { if (Realiza_Transicion (evento)) { Ejecuta (evento); idle(); } } } else { idle(); } Pág. 2-6 Capítulo 2: Descripción general del sistema. El problema de la multilínea Como se puede observar, el sistema maneja dos tipos de eventos: • Eventos ‘normales’: los generados por funciones que no se ejecutan de forma inmediata. Es el caso de la mayoría de las funciones predefinidas. • Eventos nulos: los generan las funciones que se ejecutan de forma inmediata. Por tanto, no hay que darles tiempo para que se produzcan. Dicho de otra manera, si una función genera un evento nulo, se ejecuta de inmediato, y se pasa a ejecutar la siguiente instrucción (por eso el bucle while). Es el caso de todas las funciones internas y de algunas funciones predefinidas. La función Realiza_Transicion hace avanzar al autómata al siguiente estado, a la siguiente instrucción, y la función Ejecuta, ejecuta dicha instrucción. La función idle (le llamaremos idle general), nos permite seguir extendiendo el concepto idle. Su misión es ejecutar, cuando no hay eventos, todas las funciones idle que posee el sistema. Estas funciones realizan el procesamiento en segundo plano. Por simplificar, supongamos que el sistema sólo tiene dos funciones idle: llamada y reproducir. Está función sería: void idle(void) { idle_llamada(); /* los llamaremos ‘idle de primer nivel’ */ idle_reproducir(); } y estos idle de primer nivel son funciones de la forma: void idle_llamada(void) { for (int linea = 0; linea < MAX_NUM_LINEAS; linea++) { //si estamos esperando llamada en esta línea if (flag[linea].esperar_llamada.inicio) { if (idle_esperar_llamada_IFTEL(linea+1) == TODAVIA_NO) { //nada } else { flag[linea].esperar_llamada.inicio = 0; //desactivamos el idle fin_esperar_llamada_IFTEL(linea+1); //finalizamos la detección flag[linea].esperar_llamada.fin = 1; //activamos cambio estado } } } } Pág. 2-7 Capítulo 2: Descripción general del sistema. El problema de la multilínea Para controlarlos se utiliza una variable flag, que es una estructura con tres campos: inicio, estado y fin: • • • inicio: si está a 1 el idle de primer nivel está activo. Si está a 0 no está activo y significará que no se requiere la funcionalidad que proporciona. estado: guarda el estado en que se encuentra el idle de segundo nivel, pues cada uno de ellos tiene la estructura de un pequeño autómata, que va avanzando poco a poco hasta que cumple su función. fin: cuando se pone a 1 genera el evento asociado a la función correspondiente, de forma que el sistema pueda avanzar al siguiente estado. Son estos idle de primer nivel los responsables de recorrer todas las líneas activas e ir llamando, para cada línea, al idle de segundo nivel que es el nivel más bajo en la jerarquía de funciones idle, y es el que realmente implementa la funcionalidad (dependiente del hardware) deseada. Para que todo lo descrito funcione adecuadamente, estos idle de segundo nivel deben ejecutarse rápidamente. Para lograrlo, se han descompuesto en estados, de forma que son necesarias varias llamadas para que completen su función. De forma general tienen la siguiente estructura: int idle_esperar_llamada_IFTEL(int linea) { switch(flag[linea].esperar_llamada.estado) { case 0: /* primer estado */ /* primera parte del procesamiento */ resultado_esperar_llamada[linea] = TODAVIA_NO; /* pasamos al siguiente estado */ flag[linea].esperar_llamada.estado++; break; case 1: /* segundo estado */ /* segunda parte del procesamiento */ resultado_esperar_llamada[linea] = TODAVIA_NO; /* pasamos al siguiente estado */ flag[linea].esperar_llamada.estado++; break; case 2: /* tercer estado */ /* tercera parte del procesamiento */ resultado_esperar_llamada[linea] = OK; break; } // switch return resultado_esperar_llamada[linea]; } Esta función necesita tres llamadas para completarse. Por simplificar, se ha mostrado un caso de desarrollo simple (pasa de un estado al siguiente), pero lo habitual es que el desarrollo no sea simple, sino que se salte entre estados, hasta que se den las condiciones necesarias para llegar al último estado y salir retornando OK, momento en el que la función idle de primer nivel activará el evento asociado y la aplicación avanzará al siguiente estado. Si el desarrollo no es simple, el número de llamadas a la función, hasta que retorne OK, no coincidirá con el número de estados. Pág. 2-8 Capítulo 2: Descripción general del sistema. El problema de la multilínea En algunos casos especialmente complejos, no es suficiente con tener estados, sino que hay que recurrir a crear subestados (estados dentro de un estado). Todas las funciones idle de segundo nivel están acompañadas por dos funciones: • función inic: realiza labores de inicialización, casi siempre inicializando variables o arrancando temporizadores que después utilizará la función idle de segundo nivel para transitar adecuadamente entre sus estados. Por eso, se ejecuta siempre antes de activar la función idle de segundo nivel. En el siguiente apartado se puede ver un ejemplo de este tipo de funciones. • función fin: libera recursos, cierra ficheros o deja al procesador de señal en un estado conocido. Es la complementaria a la anterior. Se ejecuta después de que la función idle de segundo nivel haya terminado. Por ejemplo, la función fin_esperar_llamada_IFTEL vacía la pila que se utiliza internamente para guardar los retornos de subrutinas (necesario por si la llamada anterior acabó haciendo un goto desde dentro de una subrutina), como se muestra a continuación. int fin_esperar_llamada_IFTEL(int linea) { // vaciamos la pila utilizada para guardar retornos de subrutinas inicializa_pila_gosub(linea+1); return 0; } En la siguiente figura se resume gráficamente todo lo anterior. Pág. 2-9 Capítulo 2: Descripción general del sistema. El problema de la multilínea Pág. 2-10 Capítulo 2: Descripción general del sistema. El problema de la multilínea 2.5.3 Utilización de las funciones idle Cuando el intérprete encuentra una instrucción, busca la función que la implementa y la ejecuta. Si se trata de una función interna o una función predefinida que genera un evento nulo, cuando acaba su ejecución se avanza a la siguiente instrucción. Pero si se trata de una función predefinida que genera un evento ‘normal’, esta función debe llamar a la función ‘inic’ y activar la función idle de segundo nivel poniendo a 1 el campo ‘inicio’ de la variable flag. Siguiendo con el ejemplo anterior, cuando el intérprete encuentre la instrucción esperar_llamada, llamará a la función __ESPERAR_LLAMADA, que tiene el siguiente aspecto: int __ESPERAR_LLAMADA (int linea, tipo_list tipos, arg_list arg) { // llamamos a la ‘función inic’ inic_esperar_llamada_IFTEL(linea+1); // del resto se encarga 'idle_esperar_llamada' return 0; } Y la función inic_esperar_llamada_IFTEL realiza las siguientes tareas: int inic_esperar_llamada_IFTEL(int linea) { // configuramos la tarjeta IFTEL configurar(DIRECC_IFTEL[linea]); // contador de tonos de llamada recibidos a cero rings_detectados[linea] = 0; // empezaremos por el primer estado de la función idle de segundo nivel flag[linea].esperar_llamada.estado = 0; // activamos función idle de segundo nivel flag[linea].esperar_llamada.inicio = 1; return 0; } A partir de este momento la ejecución de esta función se hace en segundo plano. El intérprete se queda esperando a que se active el evento asociado a esta función, cosa que ocurrirá cuando la función idle de segundo nivel retorne OK, lo cual provocará que la función idle de primer nivel ponga a 1 el campo ‘fin’ de la variable flag. Si esta función debe retornar algún código, lo pondrá en la variable interna resultado_funcion_predefinida en su función ‘fin’, de forma que pueda ser consultado ejecutando la función interna RESULTADO_ANTERIOR inmediatamente después. Esto se debe a que cuando acaba la función ESPERAR_LLAMADA no conocemos todavía el resultado, pues se ejecuta en segundo plano. Este es el mecanismo que se debe utilizar para todas las funciones predefinidas que generan eventos ‘normales’. Las Pág. 2-11 Capítulo 2: Descripción general del sistema. El problema de la multilínea funciones internas no alteran el valor de la variable resultado_funcion_predefinida. La ventaja de utilizar la función RESULTADO_ANTERIOR es que, al tratarse de una función interna, la consulta se hace con un switch y evitamos tener que hacer múltiples if para consultar el valor de dicha variable interna. La instrucción esperar_llamada no devuelve ningún código, pero para ilustrar lo anterior, vamos a considerar la instrucción ODBC_CIERRA_BD, que sí retorna un código para indicar si ha podido cerrar la base de datos o bien se ha producido algún error: ODBC_CIERRA_BD ("buzon","access";); funcion_interna RESULTADO_ANTERIOR(;) switch case "-2" : case "-3" : case "-4" : case "-5" : GOSUB fallo_sistema:; break; fin_funcion_interna; Pág. 2-12 Capítulo 2: Descripción general del sistema. El problema de la multilínea 2. DESCRIPCIÓN GENERAL DEL SISTEMA. EL PROBLEMA DE LA MULTILÍNEA ______________________________________________________ 2-1 2.1 Introducción _______________________________________________________2-1 2.2 ARQUITECTURA HARDWARE ______________________________________2-1 2.2.1 2.2.2 2.2.3 Interfaz de línea telefónica _______________________________________________ 2-2 Placa VISHA__________________________________________________________ 2-2 Dispositivos auxiliares __________________________________________________ 2-3 2.3 EL PROBLEMA DE LA MULTILÍNEA________________________________2-4 2.4 SOLUCIONES ADOPTADAS ________________________________________2-5 2.5 FILOSOFIA MULTILINEA __________________________________________2-5 2.5.1 2.5.2 2.5.3 Introducción __________________________________________________________ 2-5 Extensión del concepto idle ______________________________________________ 2-6 Utilización de las funciones idle __________________________________________ 2-11 Pág. 2-13