Skip to main content
Utility

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.

  • NGSI-LD
  • LoRaWAN
  • Device
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 Device entity 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 as alternateName.
  • Processing payloads from other providers: With the provider "Other" you map arbitrary JSON fields and fill missing values statically.

How the node works​

1

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.

2

Resolve fields

Per attribute the order is: static value, mapped path, TTN default path, default value. Values that look like JSON are parsed.

3

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.

4

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​

FieldTypeDefaultMeaning
ProviderSelectTTI / TTNTTI/TTN enables the automatic TTN default paths and the Auto-fill TTI button; Other shows the RSSI and SNR fields instead.
NormalizeCheckboxdisabledScales 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 JSONText areaemptySample payload of the sensor. The detected paths appear as a selection in all path fields.
ContextTextemptyCustom @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​

FieldTypeDefaultMeaning
Auto-fill TTIButton–Fills the path fields with the standard paths of a The Things Stack payload (list below).
ID PrefixTexturn: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.
NamePath / staticemptyReadable device name; overridden by the name mapping and the static value.
DeviceIDPath / staticemptyTechnical device identifier; forms the entity ID. TTN fallback: end_device_ids.device_id. Older flows with the fields mapAlternateName / staticAlternateName are migrated automatically.
DevEUIPath / staticemptyDevice EUI. TTN fallback: end_device_ids.dev_eui. Serves as entity ID when no DeviceID is present.
Provider InfoPath / staticemptyValue for dataProvider. Without a mapping the selected provider is used; Other produces no attribute.
Contact DatastaticemptyStatic value for the attribute contact.
Latitude, LongitudePath / staticemptyLocation. TTN fallback: the location paths from the path list below the table. Only valid coordinates produce a GeoProperty.
RX MetadataPath / static (JSON)emptyTTI/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, SNRPath / staticemptyProvider "Other" only: direct paths to the signal values. If absent they are taken from the RX metadata.
Battery LevelPath / staticemptyBattery 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 MappingsListemptyOne 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​

FieldTypeDefaultMeaning
Detected DeviceIDsListemptyDeviceIDs 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 MappingListemptyOne 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​

FieldTypeDefaultMeaning
Enable Transformation ScriptCheckboxdisabledSwitches the script on.
Transformation Script (JS)Code editoremptyJavaScript 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​

PropertyTypeMeaning
msg.payloadobjectThe 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_messageobjectMarkers of a TTN/TTI payload; they enable the default paths even with provider "Other".
msg.payload.received_attimestampUsed 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)objectFor 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"}
}
]
FieldMeaning
idID prefix plus DeviceID, else DevEUI, else unknown-<random id>.
typeAlways Device.
@contextConfigured context or default context as an array.
deviceEUI, deviceIDIdentifiers as strings, only when present.
nameOrder: static name, name mapping, mapped payload name, DeviceID.
alternateNameOnly when the name mapping yields a name that differs from the DeviceID; holds the DeviceID.
contactStatic contact data, only when set.
locationGeoProperty with a GeoJSON Point [longitude, latitude], only for valid coordinates.
gatewaysArray with gatewayId, eui, rssi, snr per gateway from the RX metadata.
rssi, snrDirectly mapped values or those of the gateway with the highest RSSI.
batteryLevelBattery level 0 to 1.
dataProviderProvider info or selected provider, not for "Other".
Promoted readingsProperties named by the name mapping (TTN/TTI only, only if the name is not yet taken).
Custom attributesTargets of the custom mappings; GeoJSON Points become GeoProperty, everything else Property.
dateObservedISO 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.

Step 1

MQTT In

An MQTT In node receives the uplink messages, for example from the MQTT integration of TTN/TTI.

Step 2

Device Info

The node produces a Device entity using the TTN default paths without further configuration.

Step 3

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​

IndicatorMeaning
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 in settings.js the list is empty after a restart. Clear it with the trash icon in the dialog or via POST /device-info/<node id>/seen-devices/clear.
  • Admin endpoints: GET /device-info/<node id>/seen-devices and the clear endpoint require the Node-RED permissions device-info.read and device-info.write respectively.
  • Transformation script: The script has full access to entity, payload and msg and runs with a 2 s time limit. Allow only trusted persons to edit the flow.
  • Battery level: The Smart Data Model expects batteryLevel in the range 0 to 1. Percentages are scaled down even without Normalize; negative values are set to 0.
  • Timestamps: dateObserved and all observedAt annotations 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.

References​