Ces routes gèrent l’arborescence des actifs de Hermetry Twin : sites, bâtiments, étages, pièces, lignes, machines et vos propres types, où chaque appareil est placé, ainsi que les relations entre actifs et appareils. Les concepts sont décrits dans Asset hierarchy. Les chemins se trouvent sous https://<your host>/tenant/<tenantId>.
Routes
| Méthode | Chemin | Permission | Fonction |
|---|---|---|---|
| GET | /api/iot/assets | iot_asset.assets.read | Liste sous forme de page plate, d’arbre, de tableau Sites & Assets ou de son résumé |
| POST | /api/iot/assets | iot_asset.assets.manage | Créer un actif |
| GET | /api/iot/assets/{assetId} | iot_asset.assets.read | Un actif avec ses ancêtres et ses compteurs |
| PATCH | /api/iot/assets/{assetId} | iot_asset.assets.manage | Modifier un actif |
| DELETE | /api/iot/assets/{assetId} | iot_asset.assets.manage | Supprimer un actif |
| POST | /api/iot/assets/{assetId}/move | iot_asset.assets.manage | Déplacer sous un autre parent |
| GET | /api/iot/assets/{assetId}/devices | iot_asset.assets.read | Appareils placés dessus (et en dessous) |
| POST | /api/iot/assets/{assetId}/devices | iot_asset.relations.manage | Placer jusqu’à 100 appareils dessus |
| GET | /api/iot/devices/{deviceId}/placement | iot_asset.assets.read | Où se trouve un appareil |
| PUT | /api/iot/devices/{deviceId}/placement | iot_asset.relations.manage | Placer un appareil, ou supprimer son placement |
| GET | /api/iot/relations | iot_asset.assets.read | Lister les relations |
| POST | /api/iot/relations | iot_asset.relations.manage | Créer une relation |
| DELETE | /api/iot/relations/{relationId} | iot_asset.relations.manage | Supprimer une relation |
Rôles par défaut : la lecture est une permission Utilisateur ; la modification des actifs, des placements et des relations est une permission Administrateur. Chaque modification est écrite dans le journal d’audit (les noms des champs modifiés, jamais leurs valeurs).
L’actif
| Champ | Règles |
|---|---|
name | obligatoire, 1 à 255 caractères |
kind | obligatoire ; lettres, chiffres et _, 1 à 24, enregistré en majuscules. Suggérés : SITE, BUILDING, FLOOR, ROOM, AREA, LINE, MACHINE, OTHER ; tout autre est accepté |
code | lettres, chiffres, ., _, - ; 1 à 32 ; enregistré en majuscules ; unique parmi vos actifs actifs. Omis : généré à partir du nom. |
parentId | à la création uniquement ; null ou absent pour un actif de premier niveau. Le modifier ensuite avec move. |
description | jusqu’à 1 000 caractères |
lat, lng | −90 à 90, −180 à 180 |
timezone | un nom IANA tel que Europe/Istanbul |
addressJson | jusqu’à 12 champs texte |
attributes | jusqu’à 100 clés et 16 Kio ; les clés commencent par une lettre, puis lettres, chiffres, ., _, - (jusqu’à 64) |
defaultDashboardId | un tableau de bord que le bouton Open dashboard de l’actif ouvre |
Champs en lecture seule dans les réponses : iotAssetId, path (les codes depuis le sommet, par exemple /IST/BLD-A/GF), depth, createdAt, updatedAt.
POST/tenant/{tenantId}/api/iot/assets
Créer un actif. Les champs inconnus sont refusés.
Corps de la requête application/json
{
"name": "Compressor room",
"kind": "ROOM",
"code": "CR1",
"parentId": "e5f6…",
"timezone": "Europe/Istanbul",
"attributes": {
"area_m2": 42
}
}
Exemple 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}}'
Réponses
201Créé
{
"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
},
"…": "…"
}
}
409Conflit
{
"message": "Another asset already uses this code.",
"code": "CONFLICT",
"details": {
"reason": "code_taken"
}
}
PATCH accepte les mêmes champs, sauf parentId. Un nouveau code réécrit en une seule étape les chemins de tous les actifs situés en dessous.
GET /api/iot/assets/{assetId} ajoute ancestors (racine en premier, chacun avec id, code, name, kind), effectiveTimezone (le sien, sinon celui de l’ancêtre le plus proche), childCount et deviceCount.
Liste et arbre
GET /api/iot/assets avec view :
view | Réponse | Paramètres |
|---|---|---|
list (par défaut) | { assets, total, page, pageSize }, une page plate | parentId (un UUID ou root), subtreeOf, kind, search, includeDeleted, page à partir de 1, pageSize jusqu’à 200 (par défaut 50) |
tree | L’arbre imbriqué | rootId, depth (par défaut 6, au maximum 12) ; au plus 5 000 actifs |
table | Une page du tableau Sites & Assets : chaque actif avec son emplacement, ses enfants, ses appareils et ses alarmes actives | search, kind, site (un sous-arbre), parentId, alarmed=true, sort (name, kind, location, children, devices, alarms avec :asc ou :desc), page, pageSize |
summary | Les compteurs et les sites avec leurs alarmes par gravité |
Les chiffres d’alarmes ne sont inclus que si l’appelant est autorisé à lire les alarmes.
Déplacer
POST /api/iot/assets/{assetId}/move
{ "parentId": "c3d4…" }
null en fait un actif de premier niveau. Tout ce qui se trouve en dessous se déplace avec lui. Déplacer un actif sous lui-même ou sous l’un de ses descendants est refusé : « An asset cannot be moved under itself or one of its own descendants. » (400, cycle). Un chemin peut comporter au plus 1 024 caractères (too_deep).
Supprimer
Un actif qui a des actifs enfants est refusé : « This asset still has child assets: move or delete them first. » (409, has_children). Si un autre module indique qu’il utilise encore l’actif, la suppression est refusée avec un 409 nommant ce module. La suppression d’un actif met fin à ses placements et relations d’appareils ; les appareils eux-mêmes restent.
Placer des appareils
POST /api/iot/assets/{assetId}/devices
{ "deviceIds": ["6f1c…", "8b2d…"] }
Chaque appareil n’a qu’un seul emplacement : le placer ici le retire de l’endroit où il se trouvait. La réponse { "changes": [...] } liste ce qui a été déplacé. Un appareil à la fois :
PUT /api/iot/devices/{deviceId}/placement
{ "iotAssetId": "77aa…" } // or null to remove the placement
Les deux GET et PUT de .../placement répondent { "placement": { "iotAssetId": "77aa…", "path": [ { "iotAssetId": "…", "code": "IST", "name": "Istanbul plant", "kind": "SITE" }, … ] } }, racine en premier. Un appareil sans emplacement a iotAssetId: null.
GET /api/iot/assets/{assetId}/devices liste les appareils placés sur l’actif et, par défaut, en dessous (recursive=false pour l’actif seul) : search, online=true|false, sort=name|connection:asc|desc, page à partir de 1, pageSize jusqu’à 200. La réponse est { items, total, page, pageSize }.
Relations
Une relation relie deux extrémités, chacune un ASSET ou un DEVICE, avec un type : CONTAINS, MANAGES, FEEDS, MONITORS ou le vôtre (lettres, chiffres, _, jusqu’à 32, enregistré en majuscules).
POST/tenant/{tenantId}/api/iot/relations
Créer une relation. additionalInfo contient jusqu’à 20 clés et 4 Kio.
Corps de la requête application/json
{
"fromType": "DEVICE",
"fromId": "8b2d…",
"toType": "DEVICE",
"toId": "6f1c…",
"type": "FEEDS",
"additionalInfo": {
"line": "DN150"
}
}
Exemple 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"}}'
Réponses
201Créé
{
"relation": {
"relationId": "…",
"fromType": "DEVICE",
"fromId": "8b2d…",
"toType": "DEVICE",
"toId": "6f1c…",
"type": "FEEDS",
"…": "…"
}
}
409Conflit
{
"message": "This relation already exists.",
"code": "CONFLICT",
"details": {
"reason": "relation_exists"
}
}
Règles :
CONTAINSd’un actif vers un appareil correspond au placement de l’appareil (utiliser les routes de placement).CONTAINSsous toute autre forme est refusé (placement_shape), et un actif placé dans un autre actif se définit en le déplaçant, non par une relation (parentage_is_move).- Une relation doit relier deux extrémités différentes (
self_relation). - Une extrémité absente donne
404(end_not_found).
GET /api/iot/relations?entityType=ASSET&entityId={assetId} liste les relations d’une extrémité dans les deux sens ; ou filtre par fromType, fromId, toType, toId, type. limit de 1 à 500 (valeur par défaut : 200). La réponse est { "relations": [...] }.
Rechercher des appareils pour un sélecteur
GET /api/iot/devices/search?q=pump&limit=20&near=iot_asset:{assetId} renvoie { "devices": [{ "id", "name", "code", "hint" }] } : au plus 20 appareils par nom, ceux de l’appareil ou de l’actif donné en premier, avec hint indiquant l’emplacement de chacun. Elle nécessite à la fois iot_asset.assets.read et iot_device.devices.read.

