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
}
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
| Nivel | Origen | Acción |
|---|---|---|
| Parada de ciclo | Operador / fin de lote | No entrar piezas nuevas; dejar terminar las que están en curso. |
| Parada ordenada | Cambio de turno / mantenimiento | stop_token a todos los hilos; salidas seguras; registro. |
| Parada de emergencia | Seta / seguridad | Corte 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.