Control de planta

Supervisión, alarmas y parada

Una instalación no solo produce: avisa, registra, obedece al operador y se detiene de forma segura. Todo eso lo organiza un hilo supervisor alimentado por colas de eventos y órdenes, con timeouts que convierten los bloqueos en diagnósticos.

El hilo supervisor

Su trabajo: consumir la cola de eventos de todas las estaciones, mantener los KPI, alimentar el HMI y atender la cola de órdenes del operador. Como debe vigilar varias colas, usa popDurante (pop con timeout) por turnos:

struct Evento { int estacion, codigo; uint32_t serie; };
struct Orden  { enum class Tipo { Parar, Arrancar }; Orden::Tipo tipo; };

ColaBloqueante<Evento> colaEventos(64);
ColaBloqueante<Orden>  colaOrdenes(8);
std::atomic<bool> enMarcha{true};

void hSupervisor() {
    while (true) {
        Orden orden;
        if (colaOrdenes.popDurante(orden, std::chrono::milliseconds(100))) {
            if (orden.tipo == Orden::Tipo::Parar) { enMarcha = false; /* ver parada */ }
            else                                  { enMarcha = true; }
            continue;
        }
        Evento e;
        if (colaEventos.popDurante(e, std::chrono::milliseconds(100))) {
            registrarEnHistorico(e);
            actualizarHMI(e);
            if (esAlarma(e.codigo)) activarSirena();
        }
        refrescarKPIs();               // cada vuelta, aunque no haya mensajes
    }
}

Cada estación informa con un push al terminar una pieza o detectar una avería:

// dentro de hMaquina2:
if (!mecaniza(p))
    colaEventos.push({2, CODIGO_AVERIA, p.serie});

Timeouts: el watchdog de cada espera

Un wait eterno es un sensor roto disfrazado de normalidad. En una instalación seria, las esperas críticas llevan límite de tiempo y plan de avería:

// la pieza debía llegar a la prensa en menos de 10 s
if (!spEntradaPrensa.try_acquire_for(std::chrono::seconds(10))) {
    colaEventos.push({PRENSA, CODIGO_ATASCO, 0});
    entrarEnModoSeguro();
}

Lo mismo aplica a condition_variable::wait_for y a las comunicaciones (recibe con timeout). El patrón: espera acotada → diagnóstico → modo seguro.

Parada ordenada con jthread

La parada de planta no puede ser un exit(): los robots deben terminar su movimiento y los actuadores quedar en estado seguro. El mecanismo estándar es std::stop_token:

void hMaquina(std::stop_token parada, int n) {
    while (!parada.stop_requested()) {        // condición de parada
        Pieza p;
        if (!colaEntrada.popDurante(p, std::chrono::milliseconds(200)))
            continue;                          // timeout → vuelve a mirar la parada
        procesar(p);
    }
    apagarActuadores(n);                       // SALIDA SEGURA
}

int main() {
    std::jthread m1(hMaquina, 1), m2(hMaquina, 2), m3(hMaquina, 3);
    std::jthread sup(hSupervisor);

    esperarOrdenDeParada();                    // pulsador, HMI, emergencia…

    m1.request_stop();  m2.request_stop();  m3.request_stop();
    sup.request_stop();                        // los jthread hacen join al destruirse
}
📘 Nota

El timeout del popDurante es lo que hace responsive la parada: sin él, un hilo dormido en una cola vacía no vería el stop_token hasta recibir datos. Alternativa C++20: registrar un std::stop_callback que haga notify_all() sobre la condición de la cola.

Jerarquía de paradas

NivelOrigenAcción
Parada de cicloOperador / fin de loteNo entrar piezas nuevas; dejar terminar las que están en curso.
Parada ordenadaCambio de turno / mantenimientostop_token a todos los hilos; salidas seguras; registro.
Parada de emergenciaSeta / seguridadCorte de actuadores inmediato; los hilos solo vigilan y registran.

Registro e histórico

Todo evento con marca de tiempo (std::chrono::system_clock::now()) a un archivo o base de datos: el histórico es la herramienta de diagnosis. Un único hilo escritor del histórico (consumidor de la cola de eventos) evita contention en el disco y mantiene el orden cronológico.