Skip to main content

Device Profiles

A device profile describes a class of devices: how their uplinks are decoded, what fields they expose, and how those fields are displayed by default. Every device in ioX-Pulse references exactly one profile. Get the profile right once, and every device of that type works correctly forever after.

note

Where to find it: Sidebar -> Settings -> Device Profiles.

The Device Profiles list shows every profile in your workspace with its region, MAC version, and decoder status. When you have many profiles, use the search box above the list to filter by name, region, or MAC version.

Why profiles exist

A LoRaWAN device sends raw bytes. Without context, those bytes mean nothing. A profile encodes:

  • The decoder: how to turn raw bytes into named values (e.g., bytes 0-1 = temperature in °C, byte 2 = battery_percent).
  • The fields: what values your devices produce, their types, units, and aggregation behavior.
  • The display defaults: which one or two fields should appear on the device list as the at-a-glance summary.

You typically create one profile per device model (or per firmware version, if vendors break their payload format between releases).

Creating a profile

Two paths: create from the profile library (recommended), or start from scratch.

From a template

The profile library covers common sensors and devices from major manufacturers. Starting from a library template gives you a working profile without writing a line of JavaScript: depending on the entry, that includes the decoder, the field set, named downlink commands, and a prebuilt default dashboard. The tags on each entry show what is included.

  1. Settings -> Device Profiles -> + New Profile.
  2. From template.
  3. Narrow the list with any combination of:
    • Region (All, EU868, US915, AS923, AU915).
    • Manufacturer (the dropdown lists every manufacturer in the library).
    • Search, which matches device name, manufacturer, group, firmware, region, and MAC version.
  4. Templates are grouped by manufacturer. Pick the one that matches your device.
  5. Name your local copy (e.g., "Acme Thermometer v2") and Use this template.

ioX-Pulse copies the entry's decoder, fields, downlinks, and default dashboard into your workspace as an editable profile. Any later changes you make stay local; they don't affect the library entry or other accounts.

Library entries are versioned. The profile you create is a frozen copy of the version you picked: it never changes underneath you, and later library updates never touch it. To adopt a newer version of an entry, create a fresh profile from the library and move your devices over when you are ready.

Can't find your device? Use the Request a profile link in the picker to ask ioX-Connect to add it to the library.

From scratch (blank profile)

Use this when no template covers your device, or you want full control over the decoder.

  1. Settings -> Device Profiles -> + New Profile.
  2. Blank.
  3. Fill in:
    • Name (e.g., "Custom Pressure Sensor").
    • Region, MAC version, Regional parameters revision (LoRaWAN basics; ask your device manufacturer if unsure).
  4. Create.

You'll land on the profile editor with no decoder and no fields. The next sections walk through adding them.

Permission required: Partner Admin.

Cloning a profile

Already have a profile that works and want another like it? Clone it. Cloning makes an independent copy of an existing profile in your workspace, so you can reuse a working decoder and field set as the starting point for a variant (a new firmware revision, a sibling model, or a tweaked configuration) instead of rebuilding it by hand.

  1. From Settings -> Device Profiles, open the ... menu on the profile's row and choose Clone. You can also clone from the profile editor header with the Clone button.
  2. Give the copy a name. It has to be different from any profile you already have; if the name is in use, you'll be asked to pick another.
  3. Choose whether to include the profile's dashboard and workflows (both on by default).
  4. Clone. You'll land on the new profile, ready to edit.

What's copied: the LoRaWAN settings, the decoder, every field (data, mapping, config, and formula, with their identifiers and settings intact so formulas keep working), the Primary and Secondary display defaults, the downlink templates, the profile's dashboard (widget bindings are rewritten to point at the copy's fields and downlinks), and any workflows attached to the profile.

About cloned workflows: every copied workflow arrives disabled, so a clone never starts alerting on its own; review each one and enable it when you're ready. A workflow that uses a multi-cast group is skipped (multi-cast groups are tied to the original profile's devices); the clone summary tells you which ones were skipped and why.

What's not copied: per-device settings such as calibration and per-device field values. Those live on individual devices, not on the profile.

The copy is fully independent: editing it never affects the original, and vice versa.

Permission required: Partner Admin.

Letting sub-accounts customize profiles

By default the profiles you build are partner-managed: a sub-account uses them but can't change them. If a sub-account's tier has the Sub-account device profiles feature switched on, the customer can instead take their own copy of one of your profiles and tailor it to their deployment.

With that tier feature on:

  • The customer sees your profiles as read-only and can clone any of them into a copy they own.
  • They can edit that copy's decoder, fields, downlinks, and dashboard freely. The copy is independent: changes they make never touch your original, and updates you push to your original do not flow into copies already made.
  • They repoint their own devices to their copy when they're ready.
  • They can author Device profile-scoped workflows on their copy (every device on the copy at once). This is its own tier feature, Profile-scope workflows on customized profiles, on by default; see the tier table in Rules and Alerts. Without Sub-account device profiles, that scope still shows in the customer's workflow scope picker but locked, with a note that it is part of a higher plan.
  • Per-sub device-profile limit caps how many copies each sub-account may own.

Both settings live in the tier's feature set (see Tier features and quotas).

You can also control cloning per profile. The Sub-account cloning setting in the profile's Edit basics dialog decides whether that specific profile can be cloned; it is allowed by default. Set it to Not allowed to keep a profile out of reach even for tiers with profile self-service switched on: sub-accounts then can't clone it, and its decoder stays hidden from them.

Letting sub-accounts use the profile library

Separately from cloning, the Sub-account profile library tier feature lets a sub-account admin create profiles straight from the same published library you use, via an Add from library button on their Device Profiles page. They pick from exactly the entries you see in the template picker, and the profile they create is theirs: it counts against the same per-sub device-profile limit, and library updates never touch it.

This requires Sub-account device profiles to be on for the tier as well (the library is an additional way for the customer to get an editable profile of their own; without profile self-service there is nothing for them to own). Both toggles must be on in the customer tier, and your own plan must include the feature.

caution

This exposes your decoders to that tier. A customer who can clone a profile can see and copy its decoder source. Turn Sub-account device profiles on only for tiers where letting customers build on your decoders is intended. (Library entries carry their own decoders, so the library toggle on its own exposes nothing of yours.) For what the customer sees, see Customizing device profiles.

Permission required: Partner Admin.

Fields: what your devices produce

A field is a single named value coming out of a device. Open the Fields tab on the profile editor to add or edit fields.

System fields

Three fields are provided automatically on every device, no decoder or profile setup needed: RSSI (signal strength, dBm), SNR (signal-to-noise, dB), and Signal Quality (a 0-100% roll-up of the two, banded Excellent / Good / Fair / Poor). They come from the network layer, not the payload, so they are standardised across all your devices rather than depending on each decoder.

You will find them grouped under Signal in the field picker wherever you bind a field: dashboard widgets, device-profile dashboard widgets, count widgets, the analytics explorer, and workflow conditions. Signal Quality is also shown on each device's Events tab.

KindWhat it isUse when
DataA value produced by the decoder from the uplink payload.The device actually sends this reading (temperature, battery, GPS coordinate).
MappingA re-keyed or transformed view of a data field (e.g., status code -> human-readable label).The decoder produces a raw code, but humans want "Open" / "Closed" instead.
ConfigDevice-side configuration that doesn't appear in uplinks but is used when sending downlinks.Sample rate, threshold, mode flags the device exposes for control.
FormulaA computed value derived from other fields using a math expression.Unit conversions (Celsius to Fahrenheit), derived metrics (signal quality from RSSI + SNR), or any cross-field calculation.

Field types

Each field has a type:

  • Number (with optional unit, decimal places, min/max for validation and gauges)
  • String
  • Boolean
  • Date: a calendar date such as an install or service date. Always a static field, set manually rather than decoded from the payload, and usable in formulas as whole days (see Dates in formulas).

Formula fields are always Number; their type is locked.

Aggregation kind (for Number fields)

For Number fields, you can declare how the value behaves over time. This affects how Chart and History widgets roll it up:

AggregationMeaning
GaugeThe value is a current reading. Charts average it within each bucket.
CounterThe value only increases (e.g., total flow, lifetime kilometers). Charts difference it across buckets to show the delta.
AbsoluteThe value is reset/replaced rather than accumulated.

If you're unsure, Gauge is the right default for sensor readings.

Adding a field

  1. Open the Fields tab on the profile.
  2. Click + Add Field.
  3. Fill in:
    • Identifier: short snake_case name (e.g., temperature, battery_percent). This is what the decoder produces, what formulas reference, and what rule conditions use.
    • Display name: human-friendly name (e.g., "Temperature", "Battery %").
    • Kind (data / mapping / config / formula).
    • Type (number / string / boolean).
    • Source path (data fields): the key in the decoder output this field reads from (e.g., temperature). Leave it blank to make a static field instead.
    • Default value (data fields): only when there's no source path. The fixed value the field takes for a static field.
    • Retain last value (live data fields): keep showing the last reported value until it changes. See Retaining state values.
    • Semantic (Number data, numeric mapping, and formula fields): mark this field as the profile's Battery (%) source, or as one of the semantic fields (Temperature (°F), Humidity (%), and so on) so it can be charted, counted and alerted on together with the same reading on other profiles. See The battery reading and Semantic fields.
    • For Number: unit, decimal places, min/max if you want gauge widgets to scale correctly.
  4. Save.
tip

Identifiers are case-sensitive and stay forever. Renaming an identifier on a profile that's in use breaks the decoder output mapping and any rules referencing the old name. Pick a clean name up front. If you must rename, do it as a coordinated change: update the decoder output, then the field, then the rules.

Static fields and default values

Most data fields are live: they read a value from the decoder on every uplink. But some values aren't sensor readings at all, they're fixed properties of the installation. A tank's capacity, a sensor's mounting height, a site reference number: the device never reports these, but you still want them on the device and usable in calculations.

For these, create a static field: a data field with a Default value and no source path.

  • Leave Source path blank and enter a Default value. A data field is either live (has a source path) or static (has a default value), never both. The form disables one when you fill the other.
  • The default applies to every device using the profile. It's the starting value, not a per-device setting.
  • Individual devices can set their own value on the device Fields tab with Set value. That value applies to that device only and takes precedence over the profile default. Clearing it returns the device to the default.
  • A static value is configuration, not telemetry: it never creates a chart point and never fires a rule on its own. It's a fixed number, string, true/false, or date that displays on the device and feeds formulas.
  • Date fields are always static. They have no source path at all, and unlike other static fields the profile default is optional: most dates (an install date, a service date) only make sense per device, so leave the default blank and set each device's date on its Fields tab. A device works fine before its date is set; formulas that need it simply show no value until it's filled in.

Feeding formulas from a static field

Numeric static fields can be referenced by formula fields by identifier, exactly like live fields. This is the main reason to use them.

Example: a fill-level sensor reports level_cm, and you add a static tank_height_cm (default 200). A formula field fill_percent computes:

level_cm / tank_height_cm * 100

Each device can override tank_height_cm to its actual tank, and the percentage is computed per device. A formula is computed with each reading, so a changed tank size shows from the device's next reading on and the device shows no fill_percent until its first reading arrives. To apply a corrected static value to readings already stored, run Recompute history for the formula.

Retaining state values

Some devices report a value only when it changes, not on every uplink. A motorized valve sends its open/closed state when it actuates, but its routine heartbeat carries no state at all. A door sensor reports on open and on close, then stays quiet. These are state: a value that stays true until something changes it, not a measurement sampled each interval.

Without help, a field like this looks empty between changes. A dashboard tile shows "No data in this range" once the last change scrolls out of the window, and an integration reading each uplink sees the state vanish on every heartbeat.

Turn on Retain last value for the field to fix this. With it on, ioX-Pulse carries the field's last reported value forward onto later uplinks that don't include it, so every frame is complete:

  • The field is available on the toggle for live data fields only (a data field with a source path). It does not apply to static, mapping, formula, or config fields.
  • The dashboard tile keeps showing the last reported state instead of going blank.
  • Webhook and API consumers receive the value on every reading, with a marker telling them it was carried forward rather than freshly reported (see the webhooks and endpoints docs).
  • It takes effect going forward. Right after you turn it on, the field carries forward only once the device next reports the value at least once.
  • A workflow condition on a retained field is then checked on every uplink, not only on the frames that change it. This means a state like a fault stays active until the device reports it clearing, rather than only being seen at the moment it first occurs.
  • A formula or mapping field that reads a retained field sees the carried value, so it has a value on every reading.

Leave it off for ordinary measurements like temperature or battery, where a gap genuinely means "no reading."

The battery reading

Every device has one canonical battery reading. It powers the Battery column and chips (what end users see), the built-in Battery field for dashboard widgets and workflow conditions, the CSV export, and the public API. The reading comes from one of two places:

  • Automatic, from the network. For LoRaWAN devices the network asks the device for its battery and link status a few times a day, whether or not the payload contains a battery value. This works out of the box: no decoder or field setup needed. Profiles created here poll once per day; profiles installed from the profile library use the cadence their author configured. If a fleet needs a different cadence, contact support.
  • From the payload, via the Battery (%) semantic role. If the device reports battery in its payload, mark that field with Semantic: Battery (%) in the field editor. Only one field per profile can hold the role, and it must produce a number: a Number data field, a numeric mapping field, or a formula field. Formula fields cover devices that report battery voltage instead of a percentage; write a volts-to-percent formula and give it the role.

When a profile has a Battery (%) field, that field is the only battery source for its devices. The network's own battery poll is ignored, deliberately: mixing two sources with different schedules and calibrations produces confusing sawtooth charts. Clear the role to fall back to the network reading.

Values are shown as whole percentages and clamped to 0-100 for display; out-of-range decoded values render as unknown rather than skewing gauges. The role survives profile cloning and library publishing.

Semantic fields

Every profile names and keys its readings differently: one profile's temperature, another's temp_f, a third's formula. That is fine for a single-profile dashboard, but a fleet always mixes profiles, and anything that works across the fleet (an Analytics chart over all devices, a Count widget over a hand-picked set, a whole-account workflow) had no way to know that those three fields are the same reading. A semantic field is that link: mark one field per profile as the profile's reading of a given kind, and every cross-profile surface reads it from each device's own profile.

To tag a field, open it in the field editor and pick the reading under Semantic, next to Battery (%). The field must produce a number: a Number data field, a numeric mapping field, or a formula field. The choices are:

SemanticUnit
Temperature°F
Temperature°C
Humidity%
EnergykWh
Water consumptiongal
Water consumptionL
Depthft
PressurePSI
CO2ppm

A few rules keep the reading honest:

  • The unit is part of the semantic, and nothing converts. Temperature (°F) and Temperature (°C) are two different semantics; a field that reports Celsius belongs on Temperature (°C). When you pick a semantic the editor fills in the unit if the field has none, and warns when the field's own unit differs. A field holds either the Battery (%) role or a semantic, not both.
  • One field per semantic per profile. A second field with the same semantic is refused until the first is cleared. The Fields tab shows the tag next to the field type.
  • Tags belong to the profile. A customized copy an account made, or a profile installed from the library, is its own profile: tag its field too, or it stays out of the cross-profile picture. Devices on an untagged profile simply contribute nothing to that semantic; they are never dropped from the other values on the same chart.

Where semantic fields appear:

  • Analytics: a Semantic fields group in the values picker, offered whatever the device selection, listing the semantics at least one profile in scope has tagged. Filters accept them too. See Building a visualization.
  • Count widget: under Semantic fields in the field condition, for any population, including mixed hand-picked devices. See Count.
  • Whole-account workflows: as universal fields in the condition picker, next to Signal and Battery. A device whose profile has no tag never satisfies the condition. See Workflows.

Retaining a value helps only once the device has reported it at least once. But on weak coverage a state-change report can be lost entirely: you send Close valve, the valve closes, yet the one uplink that would have reported "Closed" never arrives, so the field stays on the old value.

To cover this, a downlink template can update a field the moment the device acknowledges the command. On the template, turn on Require device ACK, then set On device ACK, set field to the data field you want to reflect (for example the valve's state field) and Value to set to the value that command represents (for example Closed). When a device confirms it received that command, ioX-Pulse writes the value to the field straight away.

A few things to know:

  • By default it only fires when the device acknowledges the command, so it needs Require device ACK on. The acknowledgement confirms the device received the command, not that it physically acted on it, so this is an optimistic reflection of commanded state, best paired with hardware you have confirmed actuates reliably.
  • The value is shown as from command so users can tell it apart from an independent device reading. If the device later sends a real reading for that field, the real reading takes over.
  • The target must be a data field of type number, text, or true/false on this profile. Pair it with Retain last value on that field so the commanded value persists on later heartbeats until a real reading arrives.
  • It is off unless you configure it, and it is set per command, so different commands (open, close) map the same field to different values.

Relaxing the trigger for weak-signal sites

The device's acknowledgement rides on a single uplink frame. At sites with poor signal that frame can be lost, and if the device's regular uplinks never report the state either, the field then never updates at all, even though the command went out. For devices like that, the template's Set field when option can be switched from Device confirms (ACK) to Gateway delivers: the field is written as soon as the gateway transmits the command, without waiting for the acknowledgement.

Two things to weigh before relaxing a template:

  • Gateway delivery confirms the command was transmitted, not that the device received it. On the same weak links that lose acknowledgements, the command itself can occasionally be lost too, so the field can briefly claim a state the device never reached. Any later report from the device overrides it.
  • The downlink is still sent as a confirmed frame, so the Events tab keeps recording delivered, device-confirmed, and rejected exactly as before. A rejected entry on a relaxed template usually means the acknowledgement was lost, not that the device refused the command.

Keep the default Device confirms (ACK) unless a device's uplinks carry no state report and its sites regularly lose acknowledgement frames.

What end users see is covered in the end-user guide.

Derive fields from the decoder

Instead of adding every field by hand, you can have ioX-Pulse read your decoder and list the fields it produces.

  1. Open the Fields tab and click Derive from decoder.
  2. ioX-Pulse reads the profile's saved decoder and lists every key it can emit (Temperature, Battery, and so on).
  3. Tick the fields you want and click Add selected.

This reads the decoder you have saved on the profile, not unsaved edits in the Decoder tab. Save the decoder first, then derive.

Some decoders only emit certain fields on a specific port or for a specific payload shape. The list still shows those field names, but the Sample value column shows because no real data exercised them, and the type is a best guess you can adjust when adding the field.

To fill in real sample values and confirm types, paste a real uplink:

  1. In the Derive from decoder window, paste the payload into Paste hex bytes and set the fPort the device sends it on.
  2. Click Derive from payload.

The list refreshes with the keys and real sample values produced by that payload. This is the quickest way to seed fields for a decoder that only outputs on an application-specific port.

Decoders: bytes in, fields out

A decoder is a JavaScript function that turns a raw uplink payload into a dictionary of named values. ioX-Pulse runs it server-side every time a device sends data.

The decoder editor

  1. Open the profile -> Decoder tab.
  2. The editor has two panels:
    • Code on the left: a CodeMirror JavaScript editor.
    • Test on the right: paste a sample payload, click Run, see decoded output.

Decoder shape (TS.0.13 spec)

Your decoder exports a single function:

function decodeUplink(input) {
// input.bytes -> number[] of payload bytes
// input.fPort -> the LoRaWAN port the device used
// input.recvTime -> a Date object (when the message arrived)

return {
data: {
temperature: input.bytes[0] - 40, // example: signed byte centered on 40
battery_percent: input.bytes[1],
},
warnings: [], // optional, surfaced in the device's Events tab
errors: [], // optional, marks the uplink as decode-failed
};
}

The keys you return under data must match the identifiers of your data fields. Anything you return that doesn't have a matching field is ignored (though you can see it in the test panel).

Testing a decoder

The test panel lets you paste real or synthetic payloads:

  1. Switch to the Test sub-tab.
  2. Hex payload: type or paste bytes (e.g., 1E64A7).
  3. fPort: the device's port (often 1 or 2; check the manufacturer's docs).
  4. Run.

You'll see the decoded data object alongside any warnings or errors. Compile errors point to the specific line in your script. Runtime errors (e.g., reading past the end of the byte array) show the actual exception.

You can also load a real payload from devices already sending on this profile: use Recent uplinks in the test panel and pick one of the last few raw frames. For Blues Notehub profiles, those samples are JSON bodies (shown with notefile / synthetic fPort labels). Flat Notehub bodies often need no decoder - Pulse identity-maps matching field keys at ingest; use Insert JSON identity template when you need transforms. Setup walkthrough: Blues Notehub.

Updating a decoder on a live profile

When you save a new decoder, ioX-Pulse:

  1. Compiles it (rejects on syntax errors).
  2. Shows you a confirmation modal with the impact: how many devices use this profile, and how many sub-accounts are affected.
  3. On confirm, switches all those devices to the new decoder. The next uplink from each device is decoded with the new logic.

Historical telemetry is not re-decoded. Old payloads stay decoded with whatever decoder was active at the time.

Permission required: Partner Admin.

Where a decoder turns bytes from the device into fields, a downlink goes the other way: a command sent to the device, such as open a valve, set a reporting interval, or change a threshold. A downlink template is a named, reusable command you define once on the profile, so any device on that profile can be sent it from its Downlinks tab.

Templates live on the profile's Downlinks tab. Click Add downlink template and fill in:

  • Key is a stable slug (letters, numbers, _, -), for example open_valve or set_interval. It identifies the command and cannot be changed after creation, so choose it deliberately.
  • Display name is what operators see when they pick a command, for example "Open valve".
  • Description is optional notes on what the command does.
  • Encoder source is a function that builds the payload (see below).
  • Sample data is an optional JSON object of default values, merged with whatever an operator supplies at send time.

The encoder

Each template owns an encoder that turns the command, plus any values an operator supplies, into the bytes the device expects. It must export function encodeDownlink(input) returning { bytes, fPort }, where bytes is an array of integers in [0, 255] and fPort is an integer in [1, 223]. Operator-supplied values (or your sample data) arrive as input.data.

function encodeDownlink(input) {
const seconds = (input.data && input.data.intervalSeconds) || 1800;
return {
bytes: [0x01, (seconds >> 16) & 0xff, (seconds >> 8) & 0xff, seconds & 0xff],
fPort: 1,
};
}

The encoder is compiled when you save. Once the template is saved, use Test encode (with an optional test payload) to confirm it produces the bytes and fPort you expect before sending it to a real device.

Confirming delivery: require device ACK

Turn on Require device ACK to send this template's downlinks as a confirmed frame: the device must acknowledge receipt on its next uplink, which ioX-Pulse records as device-confirmed. Confirmed frames use more airtime and duty-cycle headroom, so reserve them for commands where confirmation matters and keep them off high-volume commands. This is a property of the template, not a per-send choice, and it applies to downlink templates only; the device Configuration tab sends unconfirmed.

On device ACK, set field

With Require device ACK on, you can also have the acknowledgement update a field, so a value reflects the state the device was just commanded into even when the device's own report is lost. Set On device ACK, set field to the data field to update and Value to set to the value this command represents. The Set field when choice below it controls whether the write waits for the device's acknowledgement (the default) or happens as soon as the gateway transmits the command. This is the command-confirmed state option; the full use case, the caveats, and pairing it with Retain last value are covered above under Reflecting commanded state from a downlink ACK.

How operators send a saved command, and what they see, is covered in the end-user guide.

Permission required: Partner Admin.

Formula fields: computed values

Sometimes you want a value the device doesn't send directly. Example: the device reports temperature in Celsius, but you want a temperature_f field for US customers' dashboards.

Add a formula field instead of changing the decoder. A formula is computed when each reading arrives and stored with it, like a decoded field, so tiles, charts, workflows, downloads, integrations and Analytics all show the same number; see How computed values are stored and Formula and mapping fields in Analytics.

Adding a formula

  1. Fields tab -> + Add Field.
  2. Kind: Formula.
  3. Identifier: temperature_f (or whatever).
  4. Display name + optional unit (°F).
  5. Expression: temperature * 9 / 5 + 32.
  6. Apply from: how far back the formula gets values for readings already stored. A new field defaults to Earliest reading kept; see Apply from.
  7. Save.

How computed values are stored

Three kinds of value are computed rather than sent by the device: formula fields, mapping fields, and readings corrected by a per-device calibration. All three are computed when a reading arrives and stored with that reading. Nothing is recalculated when you look at it later, which gives you:

  • One number everywhere. The device list, tiles, charts, history, CSV downloads, workflow conditions, webhooks, the API and Analytics all read the stored value.
  • History that stays put. Changing a formula, a mapping or a calibration changes values from the point you choose on. Readings before that point keep the values they had.
  • History from the day a field was defined. A computed field has values from the day it was added, or from the Apply from date you chose, and never earlier than the readings your plan still keeps.
  • The current value updates on the device's next reading. A formula that reads a static field or a per-device setting picks up a changed value when the device next reports; use Recompute history to apply it to readings already stored.
  • Static values and settings are read as they are now. A recompute applies today's static values and per-device settings across the whole range; there is no history for those.

A value that could not be computed for a reading, for example a formula whose input was missing, is left empty for that reading and listed on the device's Events tab under Derived values that could not be computed, with the reason.

Apply from

Saving a formula field, a mapping field or a calibration asks how far back the change reaches:

  • Now: new readings only. Stored values stay as they are. The default when you edit an existing field or set a calibration.
  • A date: stored values from that date on are recomputed. The date picker stops at the earliest reading kept.
  • Earliest reading kept: every stored reading gets the new value. The default when you add a formula or mapping field, since a new field has no history to protect.

Anything other than Now queues a recompute, and the field shows its progress on the Fields tab ("Recomputing history (40%)", then "History recomputed through Sep 9"). Readings are kept back to your plan's retention, shown as the floor date under the choice; earlier values cannot be recomputed. An edit that leaves the expression or the mapping rules unchanged queues nothing, whatever you choose.

Recompute history

Recompute history on the profile's Fields tab rebuilds stored values over a range, for repairs and re-runs: an outage that left readings without values, a static value corrected after the fact, a formula you want recomputed with today's settings.

  1. Click Recompute history.
  2. Choose the fields to recompute. Every formula or mapping field that depends on a chosen field is recomputed with it; every other field is read as stored.
  3. Choose Apply from: a date, or the earliest reading kept.
  4. Choose a mode:
    • Fill gaps: only readings with no stored value for a field get one. Existing values stay. Use it after an outage or for a field added to an old profile.
    • Replace: every stored value of the chosen fields in the range is replaced.
  5. The estimate shows the size before you commit ("About 12,000 readings across 8 devices"). Confirm.

The run is listed under the button with its request time, fields, range, mode, status and the number of readings updated, and it can be cancelled while it is queued or running. A run that stops on an error shows the reason. The list also records runs you did not request by hand:

  • On save: an Apply from answer on a field or calibration save.
  • Initial fill: the one-time fill of existing formulas, mappings and calibrations when stored values were introduced.
  • Profile change: a device moved to another profile gets a fill-gaps run for that device, so the new profile's computed fields have history for it.

Limits: one run at a time per workspace, queued in order; a newer request for the same field replaces a queued one; up to 20 queued runs per workspace; the range never reaches earlier than the readings still kept, and an initial fill covers at most 365 days. Hourly and daily summaries for the touched days are refreshed when the run finishes them, so Analytics agrees with the recomputed values.

Permission required: the same as editing the profile's fields.

Formula syntax

Supported operators: +, -, *, /, % (modulo), ^ (power, right-associative).

Supported functions:

  • abs(x), min(a, b, ...), max(a, b, ...)
  • round(x), floor(x), ceil(x)
  • sqrt(x), pow(x, y)
  • log10(x), log2(x), ln(x), exp(x)
  • today(): the current date (UTC), for date math - see Dates in formulas

Supported constants: PI, E.

Field references use the field's identifier. You can reference data, mapping, or other formula fields, as long as there's no cycle.

Dates in formulas

Date fields participate in formulas as whole days, and today() gives the current date in the same unit, so subtracting them yields a number of days. That turns a captured date into living values like age or remaining life.

Example: add a Date field install_date, then a formula field remaining_life (unit days, 0 decimal places):

365 - (today() - install_date)
  • today() - install_date is how many days ago the device was installed.
  • The result counts down reading by reading; the current value is the one computed with the device's latest reading.
  • today() is evaluated on the day each reading arrives and stored with it, so history tables, charts and CSV exports show the true decline of remaining_life over time rather than a flat line at today's value.
  • If a device's install_date isn't set yet, remaining_life shows no value instead of a wrong one.
  • Day boundaries use UTC, and results are whole days.
  • Because the result is an ordinary Number field, workflows can alert on it (e.g. notify when remaining_life drops below 30). Note that workflow conditions are checked when a device reports, so a device that has gone silent won't trigger one.

Wrap the result in max(0, ...) if it should never go negative.

Example: a clamped score

max(0, min(100, (rssi + 120) * 2))

Maps a raw value onto a 0-100 score, clamped at the edges. Note: for signal health you don't need to write this, a built-in Signal Quality field is already provided on every device (see System fields). This example is just here to show the syntax.

Primary and Secondary display fields

The device list shows two fields per device by default: the Primary and Secondary. Set the profile-level defaults here so every device using the profile starts with sensible columns.

  1. Profile editor -> Fields tab.
  2. Each field has a Display role dropdown: None / Primary / Secondary.
  3. Pick one Primary and one Secondary.
  4. Save.

Individual devices can override these defaults on the device's Fields tab. The profile default is the fallback when there's no device-specific override.

note

Only data, mapping, and formula fields can be Primary/Secondary. Config fields can't, since they're set by the platform rather than the device.

Editing an existing profile

When you change a profile, the changes apply to every device using it:

  • Editing a field's identifier or type: can break the decoder (which outputs to the old identifier) or formulas (which reference the old identifier). Update those at the same time.
  • Adding a new field: safe. Existing devices get the new field on their next uplink (if the decoder produces it). A formula or mapping field also asks how far back to compute it for readings already stored; see Apply from.
  • Editing a formula, a mapping or a calibration: applies to new readings, and to stored readings from the point you choose; see Apply from.
  • Removing a field: the data for that field stops being decoded from uplinks going forward. Historical readings remain in the telemetry database but become orphaned.
  • Editing the decoder: applies on the next uplink from each device, as described above.

The profile editor shows the device count and sub-account count affected by your pending changes on the save confirmation modal. Pay attention to this for profiles in production use.

Frequently asked

Can a device change profile after it's registered?

Yes. Open the device's Configuration tab and pick a different profile. The next uplink decodes through the new profile's decoder. Use this when a vendor updates their payload format and you've created a new profile to match.

My decoder works in the test panel but devices show decode errors. What's wrong?

Common causes: (1) the device is sending on a different fPort than your test payload, (2) the device's actual payload differs from the manufacturer's documented example, (3) the device firmware has been updated to a new format. Open the device's Events tab, find a recent failed uplink, copy its raw payload into the test panel, and you'll see the exception inline.

Can I share a profile with another partner?

Not directly. Profiles are workspace-scoped. If you've built a useful profile, ioX-Connect can publish it into the profile library so other accounts can create profiles from it. Contact your account manager.

What's the difference between a profile and an ioX-Bridge profile?

ioX-Bridge (the LoRaWAN network server) has its own concept of a device profile (region, MAC version, mac-layer settings). A Pulse profile wraps that with the additional Pulse-side concepts: decoder, fields, formulas, display defaults. When you create a Pulse profile from a template, the Bridge-side settings are copied over alongside the decoder. You won't usually need to touch Bridge directly.