Pulsador de dispositivo (deviceButton)
Es un botón físico del DVR (N9M) que el chofer puede presionar para generar una alarma de pánico/urgencia. A nivel protocolo llega como una alarma EVEM más, con ALARMTYPE 7:
{
"MODULE": "EVEM",
"OPERATION": "SENDALARMINFO",
"PARAMETER": {
"ALARMAS": 1,
"ALARMCOUNT": 1,
"ALARMNAME": "Truck Urgent Alarm",
"ALARMTYPE": 7,
"ALARMUID": 1,
"CMDNO": 289013761,
"CMDTYPE": 1,
"CURRENTTIME": 1786014911,
"L": 8,
"LCH": 1,
"P": {
"C": 11010,
"J": "-64.186667",
"S": 0,
"T": "20260806141510",
"V": 0,
"W": "-31.398204"
},
"SER": "urgent_alarm",
"TRIGGERTYPE": 1
},
"SESSION": "00000AB5F1372EC1B64BE60F1DEED55A"
}
Lo particular de esta alarma es que, además de generar el incidente, automáticamente le pide una foto al dispositivo (vía el comando REQUESTCATCHPIC) y esa foto queda asociada al incidente en la base — es la primera alarma que dispara este flujo de snapshots.
Cómo se ve en el frontend
El incidente aparece con el tipo Pulsador de dispositivo. Desde el detalle del incidente, la solapa Galería (ícono de cámara) muestra las fotos que el dispositivo mandó automáticamente al dispararse la alarma, con la superposición estándar de canal, velocidad, fecha/hora y coordenadas GPS:

Proyectos involucrados
| Proyecto | Rol |
|---|---|
n9m-signal-processor | Recibe el EVEM/SENDALARMINFO del DVR. Mapea ALARMTYPE 7 → AlarmType.DEVICE_BUTTON y setea evento.tipo = 'deviceButton' antes de mandar el evento a la nube. |
rules-create-incident | Crea el incidente en MySQL (acá recién existe el incident.id). En el afterSave, si el tipo.nombreCorto del incidente matchea la lista configurada (SNAPSHOTS_TYPES/SNAPSHOTS_ENABLED_TYPES, o por lugares con SNAPSHOTS_ENABLED_PLACES), publica a Redis (hur_catch_pic) pidiendo la foto, incluyendo el incidentId recién generado. |
n9m-center | Suscripto a hur_catch_pic. Le manda al DVR el comando MEDIASTREAMMODEL/REQUESTCATCHPIC, armando un STREAMNAME que codifica series, tipo e incidentId juntos (ver detalle técnico más abajo). |
n9m-media-go | El DVR se conecta ahí para mandar las fotos (JPEG_SNAPSHOT). Sube cada una al bucket y, si pudo extraer el incidentId del STREAMNAME, le avisa al backend (POST /api/incidenteSnapshot) el nombre del archivo subido. |
apis (plataforma) | Expone POST /api/incidenteSnapshot, que persiste la relación incidentId ↔ nombre del archivo en el bucket (una fila por foto — un incidente puede tener varias). |
rules-complete-tipoeventos | Ya no participa de este flujo: la lógica de snapshots vivía acá originalmente, pero se movió a rules-create-incident porque en ese punto del pipeline todavía no existe el id del incidente en la base. |
Flujo de información
- El chofer presiona el botón del DVR → llega
EVEM/SENDALARMINFOconALARMTYPE 7. n9m-signal-processorlo traduce atipo: 'deviceButton'y lo manda al pipeline de eventos (rules-complete-*→motor-reglas→rules-create-incident), igual que cualquier otra alarma.rules-create-incidentinserta el incidente enmysqly, ya con elidasignado, publica a Redis (hur_catch_pic) conseries,tipo,incidentIdy los parámetros de la foto (count,interval,r,q,pt— con defaults si no vienen).n9m-centerrecibe ese mensaje y le manda al DVR elREQUESTCATCHPIC, conSTREAMNAME: "<series>_<tipo>_<incidentId>".- El DVR toma la(s) foto(s) y las manda como
JPEG_SNAPSHOTan9m-media-go, que las sube al bucket. - Por cada foto subida con éxito,
n9m-media-goextrae elincidentIddelSTREAMNAMEy hacePOST /api/incidenteSnapshotcon{incidentId, fileName}. - El frontend, al abrir el incidente, lista esas fotos en la solapa Galería.
Detalles técnicos relevantes
- Protocolo:
MODULE: MEDIASTREAMMODEL,OPERATION: REQUESTCATCHPICpara pedir la foto — mismo módulo que usan Monitor/Intercom (REQUESTTALK) y Live Video (REQUESTALIVEVIDEO), pero sinCATCHPICSTART: el DVR confirma el pedido víaCREATESTREAM(igual que en video en vivo), yn9m-media-gousa eseSTREAMNAMEcomo base del nombre del archivo. - Parámetros configurables de la foto:
count,interval,r,q,ptse leen del mensaje de Redis si vienen, y si no, caen a los defaults5,2,7,1,6respectivamente (mismos valores que ya se usaban antes de este cambio). STREAMNAMEcomo transporte delincidentId: no hay un campo dedicado enREQUESTCATCHPICpara asociar la foto a un incidente, así que se aprovecha elSTREAMNAME(formatoseries_tipo_incidentId) —n9m-media-goextrae todo lo que sigue al último_comoincidentIdal armar el callback.- Tabla
incidenteSnapshot: relaciónbelongsTo incidentesporincidentId, concreatedAtautogenerado por MySQL (DEFAULTa nivel de columna, no seteado por la app). Como puede haber varias fotos por incidente, no hay clave única sobreincidentId. - Autenticación del callback:
POST /api/incidenteSnapshotusa el mismo mecanismo que ya existía para/api/playbackSessions/callback(Basic auth con un token de servicio, no login de usuario) — hubo que agregarlo explícitamente a la lista de rutas permitidas para ese esquema de auth.