Protocolo Analytics Events (puerto 9090) — el contrato real¶
Fase 0 completada. Verificado en vivo contra XProtect VMS 2026 R1 el 2026-09-20. El contrato se extrajo de
XProtect Event Server\MIPPlugins\VideoOS.Analytics.Events.Service\VideoOS.Analytics.Events.Service.dllporque la documentación no alcanza para hacerlo andar.
Resultado¶
Evento inyectado → aceptado → asociado a la cámara correcta → dispara la definición de alarma filtrada por tipo + cámara. La discriminación por zona funciona de extremo a extremo.
Prueba de control: se enviaron Hanwha Intrusion_1 y Hanwha Intrusion_2.
Ambos aceptados, pero sólo el _2 generó alarma, porque sólo el _2 tiene
definición de alarma. Es exactamente el comportamiento buscado.
Las tres trampas¶
El servidor siempre responde HTTP/1.1 200 OK, incluso cuando descarta el
evento. El rechazo viene en el cuerpo de la respuesta. Si se manda y se
cierra el socket sin leer, todo parece funcionar y no llega nada. Costó
varias vueltas.
1. El namespace es obligatorio¶
El servidor busca con XPath:
Sin el namespace el XPath no matchea, Message queda nulo y responde
Warning: Event message not known.
2. El nombre del tipo va en <Message>, no en <Type>¶
Contraintuitivo pero es así. <Message> debe ser exactamente el nombre del
Analytics Event definido en Management Client → Reglas y eventos → Eventos
analíticos. <Type> es texto libre y el servidor lo ignora para el matcheo.
3. El origen se resuelve por IP o FQID, nunca por nombre de cámara¶
<Source> |
Resultado |
|---|---|
<Name>CAM-01</Name> |
❌ Warning: Device not known |
<Name>Hanwha Vision TNO-4050T (192.0.2.10)</Name> |
❌ Warning: Device not known |
<Name>192.0.2.10</Name> |
✅ aceptado |
<FQID><ObjectId>{guid}</ObjectId></FQID> |
✅ aceptado (preferido) |
El componente se llama HostResolver.cs: resuelve por host, no por nombre
amigable.
Para el servicio conviene el FQID, porque identifica el canal exacto —
con la IP, un hardware multicanal es ambiguo. El GUID sale del MIP SDK
((Get-VmsCamera).Id), que es de donde ya sacamos el inventario.
XML que funciona¶
<?xml version="1.0" encoding="utf-8"?>
<AnalyticsEvent xmlns="urn:milestone-systems">
<EventHeader>
<ID>3f2504e0-4f89-11d3-9a0c-0305e82c3301</ID>
<Timestamp>2026-09-21T02:58:22.903Z</Timestamp>
<Type>Analytics</Type>
<Message>Hanwha Intrusion_2</Message>
<CustomTag>HanwhaZoneBridge</CustomTag>
<Source>
<FQID><ObjectId>89d3bfd1-f5c8-4f6d-8805-82a510570cf9</ObjectId></FQID>
</Source>
</EventHeader>
<Description>slot 2 = area 2 = Porton Norte</Description>
<Location>lab</Location>
<Vendor><Name>HanwhaZoneBridge</Name></Vendor>
</AnalyticsEvent>
Se manda por socket TCP plano al 9090 y se lee la respuesta.
Respuestas del servidor¶
| Cuerpo | Significado |
|---|---|
| (vacío) | ✅ aceptado |
Warning: Event message not known |
El <Message> no matchea ningún Analytics Event |
Warning: Device not known |
No pudo resolver el origen |
Error: Invalid data |
XML mal formado (también responde 400) |
El servicio debe tratar cualquier cuerpo no vacío como fallo y loguearlo. Son exactamente los dos errores que se van a ver en producción: tipo mal escrito, o cámara que ya no existe.
Configuración del lado Milestone¶
Verificado en Herramientas → Opciones → Eventos analíticos:
- Habilitado: sí
- Puerto: 9090
- Seguridad → Eventos permitidos de: Todas las direcciones de red
Si se pasa a Direcciones de red especificadas, hay que agregar la IP del host
donde corra HanwhaZoneBridge. Como el servicio va a correr en el mismo
servidor, conviene dejarlo así o listar explícitamente esa IP.
El listener no necesita reinicio del Event Server para tomar tipos nuevos. Se probó reiniciando el servicio y el comportamiento no cambió: el problema era el formato, no un caché. Tipos creados por Configuration API quedan utilizables enseguida.
Crear los Analytics Event Types por código¶
No hay cmdlet dedicado en MilestonePSTools; va por Configuration API:
$inv = Get-ConfigurationItem -Path "/AnalyticsEventFolder" |
Invoke-Method -MethodId AddAnalyticsEvent
($inv.Properties | Where-Object Key -eq 'Name').Value = 'Hanwha Intrusion_2'
($inv.Properties | Where-Object Key -eq 'Description').Value = 'Generado por HanwhaZoneBridge'
$inv | Invoke-Method -MethodId AddAnalyticsEvent
Y la definición de alarma:
$inv = Get-ConfigurationItem -Path "/AlarmDefinitionFolder" |
Invoke-Method -MethodId AddAlarmDefinition
($inv.Properties | Where-Object Key -eq 'EventTypeGroup').Value = 'a96692c8-51b1-4f87-b12c-0d3d9cbfc5a4' # Analytics Events
($inv.Properties | Where-Object Key -eq 'EventType').Value = '<guid del analytics event>'
($inv.Properties | Where-Object Key -eq 'SourceList').Value = (Get-VmsCamera | ? Name -eq 'CAM-01').Path
($inv.Properties | Where-Object Key -eq 'Name').Value = 'Intrusion - Porton Norte'
$inv | Invoke-Method -MethodId AddAlarmDefinition
El GUID del grupo Analytics Events es constante:
a96692c8-51b1-4f87-b12c-0d3d9cbfc5a4.
Ojo:
EventTypeGroupyEventTypehay que setearlos juntos antes de reinvocar. Si se setea sólo el grupo, falla con "Triggering event type is out of range".
Qué quedó creado en el sistema durante la prueba¶
Para limpiar o reutilizar:
- Analytics Event Types:
Hanwha Intrusion_1,Hanwha Intrusion_2 - Alarm Definition:
Intrusion - Porton Norte (CAM-01 zona 2) - Script en el escritorio:
enviar_analytics_event.ps1
Conviene dejarlos: son la base de la Fase 1 y sirven de referencia viva.