Control de la maquinaria de un ascensor

Está práctica consiste en escribir una aplicación que controla la maquinaria de un ascensor ya existente para que se comporte de la manera descrita en este documento.

Los detalles técnicos relativos a la maquinaria del ascensor y la descripción de la interfaz de control de dicha maquinaria que ha de usarse para escribir la aplicación de control están descritos en el documento HTML que se encuentra la página web de la asignatura. Dicho documento describe los detalles de cómo integrar el software de la aplicación de control con el software que simula el ascensor y su maquinaria. También describe un conjunto de funciones que permiten actuar sobre la maquinaria y consultar su estado, detallando los argumentos a pasar y el comportamiento del ascensor al invocar cada función. Finalmente, el documento describe una lista de constantes que definen los valores concretos de cada parámetros de funcionamiento del ascensor (velocidad, número de plantas, altura de cada planta, posiciones de los topes de recorrido en el hueco del ascensor, etc.).

Indice

Especificación del comportamiento del ascensor

Inicialización de la maquinaria

Cuando se visualiza el simulador, la posición vertical del ascensor en el hueco es impredecible (se genera de forma aleatoria). Además, cuando la cabina está detenida entre dos plantas, es imposible obtener información sobre la posición en el hueco. A menos que dé la casualidad de que la cabina se halle detenida justo en una planta, será necesario mover la cabina hasta que los sensores disponibles nos permitan conocer una posición que el algoritmo de control pueda establecer como punto de inicio de la actividad. Para ello tenemos dos opciones:

Control de la puerta

La puerta del ascensor ha de permanecer cerrada mientras no tengan que subir o bajar pasajeros. Esto quiere decir que cuando el ascensor se halle sin actividad y detenido en una planta la puerta se mantendrá cerrada. Los únicos momentos en los que se abrirá la puerta serán:
Existe un sensor que indica si hay algún obstáculo interpuesto en el camino de la puerta, pero este sensor no controla el motor de la puerta para provocar su apertura automática. Si la puerta se está cerrando con un obstáculo interpuesto, el motor continuará insistiendo en la operación de cerrado, aunque la puerta no llegará cerrarse (se suponen los mecanismos de seguridad adecuados para que la puerta no ejerza demasiada presión sobre el obstáculo). Es responsabilidad del módulo de control detectar el obstáculo y abrir nuevamente la puerta. La secuencia de control de la puerta será la siguiente:

Atención a las peticiones y movimiento de la cabina

Se pretende conseguir el comportamiento adecuando para funcionar en un edificio de vecinos. El ascensor realiza ciclos de subida y bajada alternantes. Es posible pasar también a estados de inactividad en los que no hay peticiones. Un ciclo de subida no implica que la cabina se mueva hacia arriba, sino que el ascensor está atendiendo peticiones para que un supuesto usuario pueda viajar desde una planta inferior a una superior: es posible que el ascensor tenga primero que bajar a un piso inferior para recoger al usuario que luego pulsará el botón de un piso superior, sin embargo, el ascensor se encontraría en ciclo de subida.
Es decir, los ciclos de subida y bajada son estados lógicos del autómata de control, independientemente de si la cabina sube o baja. El funcionamiento que se prentende es el siguiente:
El ciclo de subida termina cuando se cierra la puerta y no queda ninguna petición de la botonera interna por encima de la planta actual. Si quedan otras peticiones pasamos al ciclo de bajado. Si no quedan más peticiones se pasa al estado de inactividad.

El ciclo de bajada termina cuando no queden más destinos por debajo de la planta actual. Si no queda ningún destino se pasa al estado de inactividad. Si no, se pasa al estado de subida de nuevo.

Este estado termina cuando se aparece una petición cualquiera de una planta que no sea la actual.

Consejos para la implementación

Procedimientos básicos de control

La mayoría de sensores disponibles no genera ningún tipo de interrupción cuando se activan. Esto incluye a los sensores de planta y a los sensores de fin de carrera. Los únicos sensores que generan interrupciones son los botones de las botoneras interna y externa y el comparador que indica el encoder ha acumulado 2700 pulsos. Para estos dos sensores existen funciones que permiten indicar qué thread debe ser avisado mediante el signal que nosotros elijamos. Una vez programado esto, tenemos que programar la función de atención al signal en el thread correspondiente para que se ejecute cuando el signal sea enviado por el simulador.

Para controlar el movimiento del ascensor podríamos usar una estrategia simple tal cómo activar el motor y establecer una temporización que nos avise en el instante en que tenemos que para el motor o cambiar la velocidad. El problema de este estrategia, es que no tiene en cuenta los errores introducidos por el simulador, que se corresponderían en la realidad con tiempos no constantes de arrancada y de frenado. Por tanto, es preferible seguir una estrategia donde los diferentes sensores disponibles nos permitan corregir continuamente las diferencias existentes entre las predicciones del control y el comportamiento de la maquinaria.

Una estrategia sencilla de verificación y corrección consiste en verificar que la planta actual por la que pasa la cabina cada vez que se activa un sensor de planta coincide con la planta prevista por el módulo de control. Podría pasar, por ejemplo, que se averiase un sensor de planta y perdiéramos la cuenta del número de plantas al faltarnos un sensor.

Otra estrategia recomendable consiste en tener en cuenta el desbordamiento del contador del encoder que nos avise mediante un interrupción en lugar de intentar consultar continuamente el sensor de planta que podría pasar desapercibido si el thread en cuestión no se puede ejecutar durante el breve paso de la cabina por el sensor.

En cuanto la parada en la planta de destino, es importante la precisión ya que el mecanismo de la puerta no abrirá si la cabina no se encuentra dentro de un margen de tolerancia alrededor de la altura de la planta. Por esa misma razón, es importante basarse en la interrupción del contador del encoder, que está directamente relacionada con la posición de la cabina, en lugar de basarse en el tiempo transcurrido desde que se puso en marcha el motor de la cabina.

El movimiento de la cabina siempre debe usar la velocidad lenta para arrancar y para parar, y la velocidad rápida para viajar entre plantas. La secuencia de movimiento resultante para cualquier desplazamiento de la cabina entre dos plantas será, por tanto, la siguiente:

Autómatas

Es evidente que existen varios autómatas en el funcionamiento del ascensor para controlar diversos comportamientos. Algunos de ellos (si no todos son):
Es muy recomendable dibujar un esquema de cada autómata, especificando los distintos estados por los que pasa y, sobre todo, las transiciones especificando los eventos o condiciones que hacen que pase a un nuevo estado. Es conveniente prever también qué ha de hacer el autómata en cada estado si se dan condiciones imprevistas en el funcionamiento normal, aunque la implementación de todas las condiciones anormales podría llegar a ser tediosa, por lo que esto último depende de la meticulosidad de cada autor en el diseño. Resumiendo, es opcional tratar posibles condiciones excepcionales que nunca deberían darse pero que podrían aparecer en caso de avería de algún sensor, etc.

La implementación de los autómatas en C sigue siempre un esquema muy sencillo:

/* Es conveniente definir simbolos para hacer mas claro el codigo */
#define AUTOMATA1_INACTIVO 0
#define AUTOMATA1_SUBIENDO 1
#define AUTOMATA1_BAJANDO 2
/* funcion que contiene un bucle de control con un autómata */
void f()
{
    /* Variable que contiene el estado actual: Inicializada con Einicial */
    int estado=INACTIVO;

    while (1)
      {
      switch(estado)
          {
          case AUTOMATA1_INACTIVO:
             ...acciones al entrar en el estado 0...
             /* espera a que se cumpla alguna condicion para la transición
                a otro estado, por ejemplo un sensor o una variable booleana
                cambiada en otro proceso */
             while (numero_destinos()==0) usleep(20000);
             /* indica el nuevo estado al que se pasa:
                puede depender de la condicion de la transición. */
                if (destinos_encima()>0) estado = AUTOMATA1_SUBIENDO;
                if (destinos_debajo()>0) estado = AUTOMATA1_BAJANDO;
             break;

          case AUTOMATA1_SUBIENDO:
             ...acciones al entrar en el estado 1...
             break;

          case AUTOMATA1_BAJANDO:
             ...acciones al entrar en el estado 2...
             break;
          }
      }
}

División en tareas

Un problema de diseño es como coordinar los distintos autómatas que controlan diversos aspectos. En algunos casos, dichos autómatas van de un estado inicial a uno final y no tienen que recordar ningún estado hasta que vuelven a ser necesitados. Este es el caso del control de la puerta, que puede ser ejecutado como una subrutina cuando la cabina se para en una planta. Una vez cerrada la puerta, la función que implementa el autómata podría retornar devolviendo el control ya que no es necesario recordar el estado de la puerta una vez cerrada.

Sin embargo, hay otros autómatas que tienen que cambiar de estado simultáneamente. Un ejemplo podría ser qué pasa si se pulsa un botón interior cuando la cabina está subiendo hasta la petición más alta en el comienzo de un ciclo de bajada. Si la petición está en el camino del ascensor, el ascensor debe pasar al ciclo de subida de nuevo y detenerse en dicha planta. En este caso, el autómata de movimiento de la cabina no puede ejecutarse como una subrutina dentro de los estados del autómata del control de destinos ya que éste último puede cambiar de estado durante el viaje de una planta a otra.

Para poder implementar esto, también es posible asignar una tarea separada a cada autómata ejcutando un bucle infinito como el de la función mostrada arriba. La comunicación entre las tareas se puede lograr mediante variables compartidas y semáforos productor-consumidor.

Lectura de las peticiones de las botoneras

Un detalle a tener en cuenta es la forma en que funcionan las botoneras. Cada botonera (la interna o la externa) se lee con una función que retorna un número entero que contiene un mapa de bits que representa los botones que han sido pulsados y que siguen activos (luz encendida). Aunque podemos recibir un signal cuando se pulsa un botón, al leer el estado de las botoneras no podemos saber qué botón o botones se acaban de pulsar. Ni siquiera en cual de las botoneras.

Para poder controlar esto es necesario distinguir entre el estado de las botoneras y la lista de peticiones ya recibidas, lo que viene a ser una copia del estado de las botoneras guardada por nuestra aplicación. Si cada vez que consultamos las botoneras guardamos una copia de su estado, al recibir un nuevo signal podemos compararlas bit a bit y saber qué bits se han activado desde la última vez que la consultamos. Para una comparación de bits podemos usar un código como este:

int mapaint_old=0;

compara_botint ()

{
   int mapa;
   int mascara, cambios;
   mapa = lee_botint();
   cambios = mapa ^ mapa_old; /* XOR de ambos mapas */
   mapaint_old = mapa;
   mascara = 1;
   for (i=0; i<N; i++)
   {
      if (cambios & mascara != 0)
         {
         printf("el boton %d ha sido pulsado\n",i);
         peticiones_internas[i]=1;
         }
      mascara = mascara << 1; /* SHIFT izquierda de la mascara */
   }
}

Otra cosa que parece conveniente es almacenar las peticiones en tablas que permitan manejarlas más facilmente. Por ejemplo:

/* Variables globales */
int peticiones_externas[N_PISOS];
int peticiones_internas[N_PISOS];

y definir funciones que nos permitan hacer consultas complejas sobre el conjunto de peticiones. Por ejemplo:

/* Retorna la primera peticion por encima de la planta actual */
int busca_primera_peticion_porencima(int planta)
{
   int peticion=-1;
   for (j=planta+1; j<N_PISOS; j++)
      if (peticiones_internas[j]==1 || peticiones_externas[j]==1) peticion=j;
   return peticion;
}

Por último, hay que tener en cuenta que es posible recibir un aviso de una botonera en cualquier momento, incluso unos milisegundos antes de tomar una decisión, por lo que no parece recomendable decidir el destino del ascensor cuando partimos de una planta, sino cuando se pulsa una tecla. Hay que tener en cuenta que el próximo destino del ascensor solo puede cambiar como consecuencia de la pulsación de un botón, por tanto no tiene sentido calcularlo continuamente si no se ha pulsado ninguna tecla. Una estrategia recomendable es:
Nota: No es posible parar en una planta sin hacer el cambio a velocidad lenta previo, es decir, no se puede parar en una planta si pulsamos el botón cuando la cabina está más cerca que la distancia de frenada reglamentaria.