Novedad: el cortafuegos de IA supervisa cada módulo y explica cada incidente con palabras sencillas. Vea cómo funciona

API de sitios y activos

Cree y lea el árbol de sitios y activos, mueva activos, coloque dispositivos en ellos y relacione activos y dispositivos mediante REST: campos, límites y errores.

Estas rutas gestionan el árbol de activos de Hermetry Twin: sitios, edificios, plantas, salas, líneas, máquinas y tus propios tipos, donde se coloca cada dispositivo, y las relaciones entre activos y dispositivos. Los conceptos están en Jerarquía de activos. Las rutas están bajo https://<your host>/tenant/<tenantId>.

Rutas

MétodoRutaPermisoFunción
GET/api/iot/assetsiot_asset.assets.readLista como página plana, como árbol, como la tabla Sitios y activos o su resumen
POST/api/iot/assetsiot_asset.assets.manageCrear un activo
GET/api/iot/assets/{assetId}iot_asset.assets.readUn activo con sus ancestros y recuentos
PATCH/api/iot/assets/{assetId}iot_asset.assets.manageModificar un activo
DELETE/api/iot/assets/{assetId}iot_asset.assets.manageEliminar un activo
POST/api/iot/assets/{assetId}/moveiot_asset.assets.manageMover bajo otro padre
GET/api/iot/assets/{assetId}/devicesiot_asset.assets.readDispositivos colocados en él (y por debajo)
POST/api/iot/assets/{assetId}/devicesiot_asset.relations.manageColocar hasta 100 dispositivos en él
GET/api/iot/devices/{deviceId}/placementiot_asset.assets.readDónde está un dispositivo
PUT/api/iot/devices/{deviceId}/placementiot_asset.relations.manageColocar un dispositivo o quitar su ubicación
GET/api/iot/relationsiot_asset.assets.readListar relaciones
POST/api/iot/relationsiot_asset.relations.manageCrear una relación
DELETE/api/iot/relations/{relationId}iot_asset.relations.manageEliminar una relación

Roles predeterminados: la lectura es un permiso de Usuario; modificar activos, ubicaciones y relaciones es un permiso de Administrador. Cada cambio se escribe en el registro de auditoría (los nombres de los campos modificados, nunca los valores).

El activo

CampoReglas
nameobligatorio, de 1 a 255 caracteres
kindobligatorio; letras, dígitos y _, de 1 a 24, se guarda en mayúsculas. Sugeridos: SITE, BUILDING, FLOOR, ROOM, AREA, LINE, MACHINE, OTHER; se permite cualquier otro
codeletras, dígitos, ., _, -; de 1 a 32; se guarda en mayúsculas; único entre tus activos activos. Si se omite: se genera a partir del nombre.
parentIdsolo al crear; null o ausente para un activo de nivel superior. Cámbielo después con move.
descriptionhasta 1.000 caracteres
lat, lng−90 a 90, −180 a 180
timezoneun nombre IANA como Europe/Istanbul
addressJsonhasta 12 campos de texto
attributeshasta 100 claves y 16 KiB; las claves empiezan por una letra, seguida de letras, dígitos, ., _, - (hasta 64)
defaultDashboardIdun panel que abre el botón Abrir panel del activo

Campos de solo lectura en las respuestas: iotAssetId, path (los códigos desde arriba, por ejemplo /IST/BLD-A/GF), depth, createdAt, updatedAt.

POST/tenant/{tenantId}/api/iot/assets

Crear un activo. Los campos desconocidos se rechazan.

Cuerpo de la solicitud application/json

{
    "name": "Compressor room",
    "kind": "ROOM",
    "code": "CR1",
    "parentId": "e5f6…",
    "timezone": "Europe/Istanbul",
    "attributes": {
        "area_m2": 42
    }
}

Ejemplo curl

curl -X POST 'https://<your-host>/tenant/{tenantId}/api/iot/assets' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Compressor room","kind":"ROOM","code":"CR1","parentId":"e5f6…","timezone":"Europe/Istanbul","attributes":{"area_m2":42}}'

Respuestas

201Creado
{
    "asset": {
        "iotAssetId": "77aa…",
        "parentId": "e5f6…",
        "path": "/IST/BLD-A/GF/CR1",
        "depth": 4,
        "code": "CR1",
        "name": "Compressor room",
        "kind": "ROOM",
        "timezone": "Europe/Istanbul",
        "attributes": {
            "area_m2": 42
        },
        "…": "…"
    }
}
409Conflicto
{
    "message": "Another asset already uses this code.",
    "code": "CONFLICT",
    "details": {
        "reason": "code_taken"
    }
}

PATCH acepta los mismos campos excepto parentId. Un nuevo code reescribe en el mismo paso las rutas de todos los activos que dependen de él.

GET /api/iot/assets/{assetId} añade ancestors (primero la raíz, cada uno con id, code, name, kind), effectiveTimezone (la propia, o si no la del ancestro más cercano), childCount y deviceCount.

Lista y árbol

GET /api/iot/assets con view:

viewRespuestaParámetros
list (predeterminado){ assets, total, page, pageSize }, una página planaparentId (un UUID o root), subtreeOf, kind, search, includeDeleted, page desde 1, pageSize hasta 200 (predeterminado 50)
treeEl árbol anidadorootId, depth (predeterminado 6, como máximo 12); como máximo 5.000 activos
tableUna página de la tabla Sitios y activos: cada activo con su ubicación, hijos, dispositivos y alarmas activassearch, kind, site (un subárbol), parentId, alarmed=true, sort (name, kind, location, children, devices, alarms con :asc o :desc), page, pageSize
summaryLos recuentos y los sitios con sus alarmas por gravedad

Las cifras de alarmas se incluyen solo cuando quien llama puede leer alarmas.

Mover

POST /api/iot/assets/{assetId}/move
{ "parentId": "c3d4…" }

null lo convierte en un activo de nivel superior. Todo lo que depende de él se mueve con él. Mover un activo debajo de sí mismo o de uno de sus descendientes se rechaza: «Un activo no puede moverse debajo de sí mismo ni de uno de sus propios descendientes.» (400, cycle). Una ruta puede tener como máximo 1.024 caracteres (too_deep).

Eliminar

Un activo con activos hijos se rechaza: «Este activo aún tiene activos hijos: muévalos o elimínelos primero.» (409, has_children). Si otro módulo indica que aún usa el activo, la eliminación se rechaza con 409 indicando ese módulo. Eliminar un activo finaliza sus ubicaciones de dispositivos y sus relaciones; los dispositivos en sí se mantienen.

Colocar dispositivos

POST /api/iot/assets/{assetId}/devices
{ "deviceIds": ["6f1c…", "8b2d…"] }

Cada dispositivo tiene como máximo una ubicación: colocarlo aquí lo saca de donde estaba. La respuesta { "changes": [...] } enumera lo que se movió. Un dispositivo a la vez:

PUT /api/iot/devices/{deviceId}/placement
{ "iotAssetId": "77aa…" }      // or null to remove the placement

Tanto GET como PUT de .../placement responden { "placement": { "iotAssetId": "77aa…", "path": [ { "iotAssetId": "…", "code": "IST", "name": "Istanbul plant", "kind": "SITE" }, … ] } }, primero la raíz. Un dispositivo sin ubicación tiene iotAssetId: null.

GET /api/iot/assets/{assetId}/devices lista los dispositivos colocados en el activo y, por defecto, también por debajo de él (recursive=false solo para el activo): search, online=true|false, sort=name|connection:asc|desc, page desde 1, pageSize hasta 200. La respuesta es { items, total, page, pageSize }.

Relaciones

Una relación une dos extremos, cada uno un ASSET o un DEVICE, con un tipo: CONTAINS, MANAGES, FEEDS, MONITORS o uno propio (letras, dígitos, _, hasta 32, guardado en mayúsculas).

POST/tenant/{tenantId}/api/iot/relations

Crear una relación. additionalInfo admite hasta 20 claves y 4 KiB.

Cuerpo de la solicitud application/json

{
    "fromType": "DEVICE",
    "fromId": "8b2d…",
    "toType": "DEVICE",
    "toId": "6f1c…",
    "type": "FEEDS",
    "additionalInfo": {
        "line": "DN150"
    }
}

Ejemplo curl

curl -X POST 'https://<your-host>/tenant/{tenantId}/api/iot/relations' \
  -H 'Content-Type: application/json' \
  -d '{"fromType":"DEVICE","fromId":"8b2d…","toType":"DEVICE","toId":"6f1c…","type":"FEEDS","additionalInfo":{"line":"DN150"}}'

Respuestas

201Creado
{
    "relation": {
        "relationId": "…",
        "fromType": "DEVICE",
        "fromId": "8b2d…",
        "toType": "DEVICE",
        "toId": "6f1c…",
        "type": "FEEDS",
        "…": "…"
    }
}
409Conflicto
{
    "message": "This relation already exists.",
    "code": "CONFLICT",
    "details": {
        "reason": "relation_exists"
    }
}

Reglas:

  • CONTAINS desde un activo hacia un dispositivo es la ubicación del dispositivo (use las rutas de ubicación). CONTAINS en cualquier otra forma se rechaza (placement_shape), y que un activo esté dentro de otro se establece moviéndolo, no con una relación (parentage_is_move).
  • Una relación necesita dos extremos distintos (self_relation).
  • Un extremo que falta es 404 (end_not_found).

GET /api/iot/relations?entityType=ASSET&entityId={assetId} enumera las relaciones de un extremo en ambos sentidos; o filtra por fromType, fromId, toType, toId, type. limit de 1 a 500 (por defecto 200). La respuesta es { "relations": [...] }.

Buscar dispositivos para un selector

GET /api/iot/devices/search?q=pump&limit=20&near=iot_asset:{assetId} responde { "devices": [{ "id", "name", "code", "hint" }] }: como máximo 20 dispositivos por nombre, primero los del dispositivo o activo indicado, con hint que indica dónde está colocado cada uno. Necesita tanto iot_asset.assets.read como iot_device.devices.read.