API y automatizaciones: cómo conviven
Un espacio puede tener automatizaciones (en el panel: "si hay movimiento, ir a la puerta y sacar una foto") y a la vez una integración que usa la API. Usar la API no apaga nada. Las dos pasan por el mismo camino de comandos, con las mismas reglas de conflicto, y todo queda con su actor.
Un solo camino
Lo que pide la API, lo que pide una persona en el panel y lo que hace una automatización llegan a la cámara por el mismo lugar: los mismos comandos permitidos, los mismos conflictos ("no se puede seguir personas con la ronda prendida"), los mismos topes y la misma auditoría. La API no tiene un atajo.
Quién hizo qué
Cada cambio queda con su actor:
| Actor | Quién |
|---|---|
user:<id> |
Una persona en el panel o la app |
automation:<id> |
Una automatización |
api:<nombre de la clave> |
Una integración, con su clave |
Se ve en la auditoría (meta.actor), en cada comando (actor en GET /cameras/{id}/commands/{command}) y en los eventos de la cámara que nacen de un pedido de la API (details.actor en los webhooks). Por eso conviene que el nombre de la clave diga qué sistema es.
Mover por la API es mover a mano
Un movimiento PTZ por la API (paso, ir a una posición, al inicio o a una posición guardada) cuenta como uno de una persona:
- El seguimiento de personas se pausa un minuto, como cuando alguien usa el pad del panel.
- La ronda de la cámara se pausa el tiempo que tenga configurado ("pausa después de moverla a mano") y después sigue sola.
Si necesitás que la cámara quede quieta más que eso, o que ninguna automatización la mueva mientras tu sistema la usa, tomá el préstamo de control.
El préstamo de control
POST /cameras/{id}/lease con el alcance control (y permiso de operar la cámara) le da a tu clave el control por ttl_seconds, de 10 a 300 (60 de fábrica). Mientras dura:
- Las acciones de las automatizaciones que mueven o cambian esa cámara se saltean: PTZ, posiciones, ronda, grabar, transmitir, cambiar ajustes (detección, "Hola Tero", marca de agua, fotos programadas), seguimiento y tapar caras. El paso queda
skippedcon el códigoleasedy el motivo ("en control por la API «nombre» hasta las 14:05"), y la automatización muestra el último salteo. - El resto del flujo sigue. Lo que no mueve ni cambia la cámara corre igual: sacar una foto, decir una frase, correr la detección una vez, sumar un cuadro al timelapse, avisar, mandar un mail o un webhook. Los pasos que siguen reciben
skipped: "leased", por si una condición quiere separar el caso. - El seguimiento no la mueve.
- Otra clave no puede moverla ni cambiar su grabación (409
camera_leased) ni tomar el control (409lease_held). Mirarla, sacarle fotos y abrir el video sí. - Las personas no se frenan. En la página de la cámara se ve "En control por la API «nombre»", y quien tiene permiso de configurarla lo puede liberar.
Reglas:
- Uno por cámara. Pedirlo de nuevo con la misma clave lo renueva: el vencimiento pasa a ser ahora +
ttl_seconds. Contesta 201 al tomarlo y 200 al renovarlo. - Tiene tope. Una clave tiene una cámara como mucho 30 minutos seguidos (
hold_ends_aten la respuesta). Renovar no llevaexpires_atmás allá de ese tope, y soltarla y volver a tomarla antes de 5 minutos cuenta como seguido. Al llegar al tope, esa clave espera 5 minutos antes de volver a tomar esa cámara (409lease_hold_limit, conretry_after,blocked_untily la cabeceraRetry-After). Otra clave sí la puede tomar. - «Liberar» frena a la clave. Si una persona la libera desde el panel, esa clave no puede volver a tomar esa cámara por 10 minutos (409
lease_revoked_by_user). Queda en la auditoría (device.lease_blocked). Las demás cámaras y las demás claves siguen igual. - Vence solo. Si tu sistema se cae, la cámara vuelve a las automatizaciones a los
ttl_seconds. Renová a un tercio del plazo (holdlo hace solo). - Muere con la clave. Una clave revocada o vencida no sostiene nada, y una de otro espacio no puede ni ver la cámara (404).
- Los tres tiempos son de la plataforma (
DEVAPI_LEASE_MAX_HOLD_SECONDS,DEVAPI_LEASE_COOLDOWN_SECONDS,DEVAPI_LEASE_RELEASE_BLOCK_SECONDS); los de arriba son los de fábrica. - Queda registrado. Tomarlo y soltarlo dejan una fila en la auditoría y un evento
lease.taken/lease.releaseden la cámara (que también sale por los webhooks comocamera.lease.*).
TypeScript / JavaScript — sdk/js/examples/portal/leases.ts
// Take control for a while: automations will not move or change this camera until you release it.
try {
return await tv.leases.hold(cameraId, async ({ lease, signal }) => {
console.log(`in control as «${lease.holder}» until ${lease.expires_at}`);
for (const direction of ['left', 'left', 'up'] as const) {
if (signal.aborted) break; // a renewal failed: stop moving
await tv.ptz.move(cameraId, direction);
}
return tv.ptz.goto(cameraId, 1200, 300);
}, { ttlSeconds: 60 }); // renewed every 20 s, released at the end (also on errors)
} catch (err) {
if (err instanceof ConflictError && err.code === 'lease_held') {
console.log('another integration is driving this camera'); // err.body.error.lease says who and until when
return null;
}
throw err;
}
Manejar las automatizaciones
| Pedido | Alcance | Qué hace |
|---|---|---|
GET /automations, GET /automations/{id} |
automations:read |
Nombre, si está prendida, pausada o con errores, sus disparadores, si se puede ejecutar y el último salteo. Sin el grafo: se edita en el panel |
POST /automations/{id}/enable, …/disable |
automations:write |
Lo mismo que el botón del panel y de la app. Apagar corta lo que estaba corriendo. Una administrada (la creó un ajuste de la cámara) prende o apaga su función en la cámara, con los mismos conflictos |
POST /automations/{id}/run |
automations:run |
La ejecuta desde su disparador Manual, con camera_id y data opcionales |
GET /automations/{id}/runs/{run} |
automations:read |
La ejecución, paso por paso (estado, código y nota de cada uno) |
Todo pide rol de administrador, como el panel. No se editan grafos por la API en esta versión.
Ejecutar desde la API
- Solo las que tienen el disparador Manual (
runnable: true). Sin él, 422no_manual_trigger. - Es de verdad, no una prueba: lo que el flujo hace en el lugar (mover, hablar, grabar) se hace.
- Pasa por las mismas reglas que cualquier ejecución: el freno del espacio (409
kill_switch), que esté prendida (409automation_inactive), los topes del motor (429automation_capped) y el antibucle. En cada paso se revisa que quien guardó la automatización siga pudiendo usar esa cámara; si no, el paso falla y queda en el historial. datallega al flujo como datos ({{ order }}en un mensaje): hasta 20 valores simples, claves[A-Za-z][A-Za-z0-9_]*, textos de hasta 500. No elige cámaras, URLs ni frases que el flujo no tenga.- El disparador queda con
trigger.actor = "api:<nombre>"y la auditoría conautomation.run_by_api.
Los eventos salen por los webhooks
Lo que pasa en la cámara mientras tanto (se movió, empezó la ronda, alguien tomó o soltó el control) llega a tu sistema por los webhooks que ya existen. No hace falta preguntar.