Device Info
The node accepts an uplink payload from The Things Network or The Things Industries (TTN/TTI) or any JSON payload and produces an NGSI-LD entity of type Device with identifiers, location, signal values, battery level and gateways. Fields are mapped by path or set statically; a name mapping translates technical DeviceIDs into readable names.
- Node type
- device-info
- Package
- @htag/node-red-contrib-hypertegrity-utility
- Inputs / outputs
- 1 / 1
- Dependency
- None external; TTN/TTI payload optional
When to use this node
LoRaWAN sensors deliver extensive uplink messages through TTN/TTI, with device identifiers, gateway metadata and decoded readings. The Device Info node extracts the device information and shapes it into an NGSI-LD entity following the Smart Data Model Device, ready to be transferred to a context broker.
Typical use cases are:
- Maintaining a device inventory in the context broker: Every uplink updates the corresponding
Deviceentity with location, signal strength and battery level. - Translating technical IDs into readable names: The name mapping replaces
eui-70b3…with "Town hall office 1" and keeps the DeviceID asalternateName. - Processing payloads from other providers: With the provider "Other" you map arbitrary JSON fields and fill missing values statically.
How the node works
Check payload
msg.payload must be an object (not an array). If the provider is TTI/TTN or the payload contains end_device_ids and uplink_message, the TTN default paths act as a fallback.
Resolve fields
Per attribute the order is: static value, mapped path, TTN default path, default value. Values that look like JSON are parsed.
Build entity
ID, type Device, context, identifiers, name, location as GeoProperty, gateways, RSSI/SNR, battery level, provider, custom mappings and dateObserved are created as NGSI-LD properties.
Optionally transform
A transformation script can adjust the entity in a sandbox before output (2 s time limit).
Result: msg.payload holds an array with exactly one NGSI-LD entity of type Device.
The node is message-driven and processes every incoming message immediately. In addition it records every DeviceID that passes through (count, first and last seen, at most 2000 entries) and offers this list in the editor for the name mapping. The list is mirrored to the node context under the key seenDevices and survives deploys and restarts only when a persistent context store is configured.
Configuration
The edit dialog has the tabs General, Mapping, Sensor Mapping and Logic. On the Mapping tab every attribute has two fields: the Payload Path on the left (selection from the sample JSON or free entry) and Overwrite (Static) on the right. A static value always takes precedence.
General tab
| Field | Type | Default | Meaning |
|---|---|---|---|
| Provider | Select | TTI / TTN | TTI/TTN enables the automatic TTN default paths and the Auto-fill TTI button; Other shows the RSSI and SNR fields instead. |
| Normalize | Checkbox | disabled | Scales a battery value above 1 by dividing by 100 (85 → 0.85). Values above 1 are in any case divided by 100 repeatedly until they fall into the range 0 to 1. |
| Sample JSON | Text area | empty | Sample payload of the sensor. The detected paths appear as a selection in all path fields. |
| Context | Text | empty | Custom @context URL or JSON array of URLs. Empty means https://context.<DOMAIN>/main-context, where DOMAIN comes from the environment variable of the same name (fallback: domain). |
Mapping tab
| Field | Type | Default | Meaning |
|---|---|---|---|
| Auto-fill TTI | Button | – | Fills the path fields with the standard paths of a The Things Stack payload (list below). |
| ID Prefix | Text | urn:ngsi-ld:Device: | Prefix of the entity ID. A missing trailing colon is added. If the DeviceID already starts with urn:ngsi-ld:, it is used unchanged. |
| Name | Path / static | empty | Readable device name; overridden by the name mapping and the static value. |
| DeviceID | Path / static | empty | Technical device identifier; forms the entity ID. TTN fallback: end_device_ids.device_id. Older flows with the fields mapAlternateName / staticAlternateName are migrated automatically. |
| DevEUI | Path / static | empty | Device EUI. TTN fallback: end_device_ids.dev_eui. Serves as entity ID when no DeviceID is present. |
| Provider Info | Path / static | empty | Value for dataProvider. Without a mapping the selected provider is used; Other produces no attribute. |
| Contact Data | static | empty | Static value for the attribute contact. |
| Latitude, Longitude | Path / static | empty | Location. TTN fallback: the location paths from the path list below the table. Only valid coordinates produce a GeoProperty. |
| RX Metadata | Path / static (JSON) | empty | TTI/TTN only: gateway array. TTN fallback: uplink_message.rx_metadata. Produces gateways as well as RSSI and SNR of the gateway with the best RSSI. |
| RSSI, SNR | Path / static | empty | Provider "Other" only: direct paths to the signal values. If absent they are taken from the RX metadata. |
| Battery Level | Path / static | empty | Battery value. TTN fallback: the battery path from the path list below the table. Output in the range 0 to 1, rounded to four decimals. |
| Custom Mappings | List | empty | One row per source path in the payload and target attribute of the entity. Dot notation in the target creates nested values inside one Property. The keys id, type, @context and scope are blocked. Stored in customMappings. |
Paths set by the Auto-fill TTI button:
end_device_ids.device_id (DeviceID)
end_device_ids.dev_eui (DevEUI)
uplink_message.locations.user.latitude
uplink_message.locations.user.longitude
uplink_message.rx_metadata (RX metadata)
uplink_message.decoded_payload.battery (battery level)
Sensor Mapping tab
| Field | Type | Default | Meaning |
|---|---|---|---|
| Detected DeviceIDs | List | empty | DeviceIDs the deployed node has already seen, with count and time of last arrival. Reload with Refresh, copy into the name mapping with +, clear the list with the trash icon. The list comes from the admin endpoint /device-info/<node id>/seen-devices. |
| Name Mapping | List | empty | One row per DeviceID and human-readable name. The name is emitted as name; if it differs from the DeviceID, the DeviceID is also emitted as alternateName. Stored in sensorMapping. |
For TTI/TTN payloads the name mapping has a second function: if an entered "DeviceID" matches a key in uplink_message.decoded_payload, that reading is promoted to its own property of the entity under the mapped name.
Logic tab
| Field | Type | Default | Meaning |
|---|---|---|---|
| Enable Transformation Script | Checkbox | disabled | Switches the script on. |
| Transformation Script (JS) | Code editor | empty | JavaScript that runs after the entity is built. Available are entity, payload, msg, console, makeProperty(value, observedAt) and the older alias formatProperty. Modify entity directly. The script is compiled once on deploy and executed per message with a 2 s time limit. |
Input
| Property | Type | Meaning |
|---|---|---|
msg.payload | object | The sensor payload. Arrays and non-objects are rejected with the error Input payload must be a non-array object. |
msg.payload.end_device_ids, msg.payload.uplink_message | object | Markers of a TTN/TTI payload; they enable the default paths even with provider "Other". |
msg.payload.received_at | timestamp | Used as dateObserved and as observedAt of the reading properties. If absent, the current time is used for observedAt only; dateObserved is then not created. |
decoded_payload (below msg.payload.uplink_message) | object | For TTN/TTI: source of the battery level and of readings promoted via the name mapping. |
Output
msg.payload holds an array with exactly one entity. Example for a TTN uplink with a name mapping:
[
{
"id": "urn:ngsi-ld:Device:eui-70b3d57ed0000001",
"type": "Device",
"@context": ["https://context.exampletown.org/main-context"],
"deviceEUI": {"type": "Property", "value": "70B3D57ED0000001"},
"deviceID": {"type": "Property", "value": "eui-70b3d57ed0000001"},
"name": {"type": "Property", "value": "Town hall office 1"},
"alternateName": {"type": "Property", "value": "eui-70b3d57ed0000001"},
"location": {"type": "GeoProperty", "value": {"type": "Point", "coordinates": [8.353, 50.0]}},
"gateways": {"type": "Property", "value": [{"gatewayId": "gw-townhall", "eui": "AA555A0000000001", "rssi": -87, "snr": 7.5}], "observedAt": "2026-10-01T08:15:00.000Z"},
"rssi": {"type": "Property", "value": -87, "observedAt": "2026-10-01T08:15:00.000Z"},
"snr": {"type": "Property", "value": 7.5, "observedAt": "2026-10-01T08:15:00.000Z"},
"batteryLevel": {"type": "Property", "value": 0.85, "observedAt": "2026-10-01T08:15:00.000Z"},
"dataProvider": {"type": "Property", "value": "TTI/TTN"},
"dateObserved": {"type": "Property", "value": "2026-10-01T08:15:00.000Z"}
}
]
| Field | Meaning |
|---|---|
id | ID prefix plus DeviceID, else DevEUI, else unknown-<random id>. |
type | Always Device. |
@context | Configured context or default context as an array. |
deviceEUI, deviceID | Identifiers as strings, only when present. |
name | Order: static name, name mapping, mapped payload name, DeviceID. |
alternateName | Only when the name mapping yields a name that differs from the DeviceID; holds the DeviceID. |
contact | Static contact data, only when set. |
location | GeoProperty with a GeoJSON Point [longitude, latitude], only for valid coordinates. |
gateways | Array with gatewayId, eui, rssi, snr per gateway from the RX metadata. |
rssi, snr | Directly mapped values or those of the gateway with the highest RSSI. |
batteryLevel | Battery level 0 to 1. |
dataProvider | Provider info or selected provider, not for "Other". |
| Promoted readings | Properties named by the name mapping (TTN/TTI only, only if the name is not yet taken). |
| Custom attributes | Targets of the custom mappings; GeoJSON Points become GeoProperty, everything else Property. |
dateObserved | ISO 8601 timestamp from received_at, only when present. |
All reading properties (gateways, rssi, snr, batteryLevel, promoted and custom values) carry the observedAt annotation.
Example flow
The package ships with an example flow. Import it in Node-RED via Menu → Import → Examples → @htag/node-red-contrib-hypertegrity-utility → device-info. Alternatively download device-info.json and import it via Menu → Import. The MQTT In node in the example has no broker and no topic; enter both after the import.
MQTT In
An MQTT In node receives the uplink messages, for example from the MQTT integration of TTN/TTI.
Device Info
The node produces a Device entity using the TTN default paths without further configuration.
Debug
A Debug node shows the array with the entity in the sidebar.
Show example flow as JSON
[
{"id": "tab1", "type": "tab", "label": "Device Info", "disabled": false, "info": ""},
{"id": "mqtt1", "type": "mqtt in", "z": "tab1", "name": "", "topic": "", "qos": "2", "datatype": "auto-detect", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 190, "y": 140, "wires": [["di1"]]},
{"id": "di1", "type": "device-info", "z": "tab1", "name": "", "providerType": "TTI/TTN", "sampleJson": "",
"mapName": "", "staticName": "", "mapDeviceID": "", "staticDeviceID": "", "mapDevEUI": "", "staticDevEUI": "",
"mapLat": "", "staticLat": "", "mapLon": "", "staticLon": "", "mapRxMetadata": "", "staticRxMetadata": "",
"mapRssi": "", "staticRssi": "", "mapSnr": "", "staticSnr": "", "mapBattery": "", "staticBattery": "",
"mapProvider": "", "staticProvider": "", "idPrefix": "urn:ngsi-ld:Device:", "staticContact": "",
"normalizeBattery": false, "sensorMapping": "[]", "customMappings": "[]",
"useTransformation": false, "transformationScript": "", "context": "",
"x": 390, "y": 140, "wires": [["dbg1"]]},
{"id": "dbg1", "type": "debug", "z": "tab1", "name": "Device Info Entity", "active": true, "tosidebar": true, "complete": "payload", "targetType": "msg", "x": 610, "y": 140, "wires": []}
]
Status and errors
| Indicator | Meaning |
|---|---|
| Grey ring "ready" | Node is deployed, no message processed yet. |
| Green dot "…:Device:eui-70b3d57ed0000001 (3 dev)" | Last entity produced successfully; in brackets the number of DeviceIDs seen so far. Long IDs are truncated at the front. |
| Red ring "Input payload must be a non-array object" | msg.payload is not an object. The message is not sent. |
| Red ring "Transformation script error: …" | The transformation script threw an error or exceeded the time limit. |
| Error "Transformation script compilation failed: …" | The script could not be compiled on deploy; the node continues without the script. |
Warning "Custom mapping <source> -> <target>: …" | A custom mapping could not be written; the others are processed. |
Errors are reported via done(err) and can be handled with a Catch node.
Notes
- Context store: The detected DeviceIDs live in the node context (
seenDevices) and are saved with a 3 s delay. Without a persistent context store insettings.jsthe list is empty after a restart. Clear it with the trash icon in the dialog or viaPOST /device-info/<node id>/seen-devices/clear. - Admin endpoints:
GET /device-info/<node id>/seen-devicesand the clear endpoint require the Node-RED permissionsdevice-info.readanddevice-info.writerespectively. - Transformation script: The script has full access to
entity,payloadandmsgand runs with a 2 s time limit. Allow only trusted persons to edit the flow. - Battery level: The Smart Data Model expects
batteryLevelin the range 0 to 1. Percentages are scaled down even without Normalize; negative values are set to 0. - Timestamps:
dateObservedand allobservedAtannotations are emitted as ISO 8601 strings in UTC. - Personal data: DeviceIDs, DevEUIs, locations and the static Contact Data can be personal data, for example when devices are assigned to individual persons or contact persons are named. Do not enter private names or addresses as contact data and check which attributes may be published before transferring them to a context broker. The list of detected DeviceIDs remains in the Node-RED context.
- Units: RSSI in dBm, SNR in dB, coordinates in decimal degrees (WGS 84), battery level as a fraction 0 to 1.