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étodo | Ruta | Permiso | Función |
|---|---|---|---|
| GET | /api/iot/assets | iot_asset.assets.read | Lista como página plana, como árbol, como la tabla Sitios y activos o su resumen |
| POST | /api/iot/assets | iot_asset.assets.manage | Crear un activo |
| GET | /api/iot/assets/{assetId} | iot_asset.assets.read | Un activo con sus ancestros y recuentos |
| PATCH | /api/iot/assets/{assetId} | iot_asset.assets.manage | Modificar un activo |
| DELETE | /api/iot/assets/{assetId} | iot_asset.assets.manage | Eliminar un activo |
| POST | /api/iot/assets/{assetId}/move | iot_asset.assets.manage | Mover bajo otro padre |
| GET | /api/iot/assets/{assetId}/devices | iot_asset.assets.read | Dispositivos colocados en él (y por debajo) |
| POST | /api/iot/assets/{assetId}/devices | iot_asset.relations.manage | Colocar hasta 100 dispositivos en él |
| GET | /api/iot/devices/{deviceId}/placement | iot_asset.assets.read | Dónde está un dispositivo |
| PUT | /api/iot/devices/{deviceId}/placement | iot_asset.relations.manage | Colocar un dispositivo o quitar su ubicación |
| GET | /api/iot/relations | iot_asset.assets.read | Listar relaciones |
| POST | /api/iot/relations | iot_asset.relations.manage | Crear una relación |
| DELETE | /api/iot/relations/{relationId} | iot_asset.relations.manage | Eliminar 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
| Campo | Reglas |
|---|---|
name | obligatorio, de 1 a 255 caracteres |
kind | obligatorio; 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 |
code | letras, 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. |
parentId | solo al crear; null o ausente para un activo de nivel superior. Cámbielo después con move. |
description | hasta 1.000 caracteres |
lat, lng | −90 a 90, −180 a 180 |
timezone | un nombre IANA como Europe/Istanbul |
addressJson | hasta 12 campos de texto |
attributes | hasta 100 claves y 16 KiB; las claves empiezan por una letra, seguida de letras, dígitos, ., _, - (hasta 64) |
defaultDashboardId | un 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:
view | Respuesta | Parámetros |
|---|---|---|
list (predeterminado) | { assets, total, page, pageSize }, una página plana | parentId (un UUID o root), subtreeOf, kind, search, includeDeleted, page desde 1, pageSize hasta 200 (predeterminado 50) |
tree | El árbol anidado | rootId, depth (predeterminado 6, como máximo 12); como máximo 5.000 activos |
table | Una página de la tabla Sitios y activos: cada activo con su ubicación, hijos, dispositivos y alarmas activas | search, kind, site (un subárbol), parentId, alarmed=true, sort (name, kind, location, children, devices, alarms con :asc o :desc), page, pageSize |
summary | Los 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:
CONTAINSdesde un activo hacia un dispositivo es la ubicación del dispositivo (use las rutas de ubicación).CONTAINSen 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.

