Skip to main content

Configure MQTT Subscriptions

availability
Platform:WebMobile
Plan Type:BasicEssentialPremiumEnterprise
User Type:RequesterFull UserAdministrator

This article explains how to configure the MQTT subscriptions used by the MaintainXยฎ On-Premise Agent. Subscriptions define which MQTT topics the agent listens to, how it decodes their payloads, and how it names the resulting sensors in MaintainX.

For installation and the base settings.json skeleton, see Setup and Configure the MQTT On-Premise Agent.

Overviewโ€‹

MQTT subscriptions are declared under the MqttSubscriptionSettings block of settings.json. Each subscription pairs a topic pattern with rules that tell the agent how to turn incoming payloads into sensor readings.

{
"MqttSubscriptionSettings": {
"Version": "v1",
"Subscriptions": [
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
],
"IgnoredTopics": []
}
}

The agent supports several subscription types. The public types are:

SubscriptionTypeWhen to Use
CustomAny MQTT broker. You control topic patterns, payload decoding, and field mapping.
SparkplugBrokers using the Sparkplug B specification.
Zigbee2MQTTBrokers fronting a Zigbee2MQTT bridge.

This page focuses on the Custom type, which is the most flexible and applies to any MQTT broker.

Subscription Anatomyโ€‹

Each entry in Subscriptions is an object with the following fields:

FieldRequiredDescription
TopicYesThe MQTT topic pattern. It supports the + wildcard per segment. The # wildcard is not supported for Custom subscriptions. See Wildcard Rules.
SubscriptionTypeYesThis field must be set to Custom for the configurations described on this page.
PayloadYes (for Custom)An object that describes how to decode the payload. See Payload Encoding.
DescriptionNoA free-text annotation. It has no effect on agent behavior and is useful for documenting your configuration.
TimestampFieldNoThe top-level JSON field whose value is used as the reading timestamp. This applies only to Json encoding.

The top-level MqttSubscriptionSettings block also accepts:

FieldRequiredDescription
VersionYesThe schema version. Set this to "v1".
SubscriptionsYesAn array of subscription objects (at least one is required).
IgnoredTopicsNoAn array of topic patterns to exclude from processing. See Ignored Topics.

Payload Encodingโ€‹

The Payload.Encoding field selects one of two decoders:

EncodingWhen to Use
RawEach topic carries a single scalar value (a number, a boolean, or a string).
JsonThe topic carries a JSON object from which the agent extracts one or more fields.

Raw Encodingโ€‹

Use Raw when the entire MQTT payload is the value to record. One subscription with Raw encoding maps to 1 sensor per matched topic.

{
"Topic": "plant/boiler/temperature",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Number",
"Unit": "ยฐC",
"FriendlyName": "Boiler Temperature"
}
}

Raw payload fields:

FieldRequiredDescription
EncodingYesThe encoding type. It must be set to "Raw".
TypeYesThe data type. It must be one of "String", "Number", "Boolean", or "Auto". See Type Auto: Automatic Type Inference.
FriendlyNameNoThe name to use for the sensor in MaintainX. It is subject to the Sensor Naming Matrix.
UnitNoThe unit of measurement (e.g., ยฐC or psi). This applies only when Type resolves to a numeric value.
DescriptionNoA free-text description shown on the sensor in MaintainX.

JSON Encodingโ€‹

Use Json when the payload is a JSON object and you want to extract one or more fields. Each field becomes a separate sensor in MaintainX.

{
"Topic": "plant/line1/+",
"SubscriptionType": "Custom",
"TimestampField": "ts",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "data.temperature",
"Type": "Number",
"FriendlyName": "Temperature",
"Unit": "ยฐC"
},
{
"Path": "data.humidity",
"Type": "Number",
"FriendlyName": "Humidity",
"Unit": "%"
},
{
"Path": "data.online",
"Type": "Auto"
}
]
}
}

Json payload fields:

FieldRequiredDescription
EncodingYesThe encoding type. It must be set to "Json".
FieldsYesAn array of 1โ€“10 field mappings. Each field mapping becomes a sensor.

Each entry in Fields accepts:

FieldRequiredDescription
PathYesThe dot-separated path to the value (e.g., data.temperature). Array indexes are supported using square brackets (e.g., data.readings[0].value).
TypeYesThe data type. It must be one of "String", "Number", "Boolean", or "Auto".
FriendlyNameNoThe name to use for the sensor. It is subject to the Sensor Naming Matrix.
DescriptionNoA free-text description shown on the sensor. This field is mutually exclusive from DescriptionPath.
DescriptionPathNoThe JSON path used to read the description from each payload. This field is mutually exclusive from Description.
UnitNoThe static unit of measurement. This applies only to numeric types. This field is mutually exclusive from UnitPath.
UnitPathNoThe JSON path used to read the unit from each payload. This applies only to numeric types. This field is mutually exclusive from Unit.

The subscription-level TimestampField names a top-level JSON field whose value will be used as the reading timestamp. The agent supports both ISO-8601 strings and Unix epoch values (seconds, milliseconds, or microseconds). When TimestampField is set, that field is excluded from sensor extraction.

Type Auto: Automatic Type Inferenceโ€‹

Type: "Auto" lets the agent decide a sensor's data type from the payload itself, instead of forcing you to declare it up front. It works for both Raw and Json encodings.

How Inference Worksโ€‹

When the first matching payload arrives, the agent inspects the value and picks a type in this order:

  1. Boolean: Values that parse as true / false (case-insensitive).
  2. Number: Any value that parses as a decimal number, including integer-looking strings such as 42.
  3. String: Anything else not covered by Boolean or Number values.

All numeric values are stored as double-precision floats. This keeps a sensor's storage stable when its values alternate between integers (3) and decimals (3.1), so you don't need to predict the future shape of your data.

Raw + Auto: JSON-Looking Payloads Are Skippedโ€‹

Auto is meant for scalar values. If a Raw subscription with Type: "Auto" receives a payload that starts with { or [, the agent silently skips it and emits a debug log. Use Json encoding when your payload is structured.

Unit Handling Under Autoโ€‹

The Unit field is only meaningful for numeric sensors. When Auto resolves to a Boolean or String, the configured Unit is suppressed and not stored on the sensor. This applies to both static Unit and dynamic UnitPath.

Example: Raw + Auto on a Wildcard Topicโ€‹

A factory line publishes one value per topic, where the topic's last segment is the sensor's identifier. The agent should pick the right type for each sensor automatically:

{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
Received TopicPayloadSensor NameInferred Type
factory/line1/oven_temp185.4oven_tempNumber
factory/line1/door_opentruedoor_openBoolean
factory/line1/stateRUNNINGstateString

Example: JSON + Auto on a Single Fieldโ€‹

A device reports a single field that may be reported as either a boolean or a numeric:

{
"Topic": "devices/+/online",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "online",
"Type": "Auto",
"FriendlyName": "Connectivity"
}
]
}
}

The first payload received determines the sensor's data type. For details on what happens if later payloads use a different type, see Data Type Locking.

Data Type Lockingโ€‹

warning

A sensor's data type is set on first creation and never updated afterward. This applies to explicit types (Number, String, Boolean) and to Auto, where the first inferred type wins.

If a later payload for an existing sensor has a different type than the one stored at creation, the agent drops the payload and logs a one-time warning per sensor:

Sensor 'oven_temp' is stored as Double but received String. Payloads with mismatched types will be dropped. To change the sensor type, delete the sensor in MaintainX and let the agent re-create it.

The warning fires once per sensor per agent lifetime to avoid log spam. You should check logs immediately after a configuration change to ensure that no type mismatches are occurring and payloads are not being dropped.

Changing a Sensor's Typeโ€‹

Because the data type is locked, restarting the On-Premise Agent does not reset it. The only way to change a sensor's type is:

  1. Delete the sensor in MaintainX.
  2. Wait for the next matching payload โ€” the agent will recreate the sensor with the new type.

Sensor Naming Matrixโ€‹

The agent derives each sensor's name from a combination of the received topic and the field configuration. The most important variable is the last segment of the actual topic that the broker delivered (referred to below as lastSegment), which is not necessarily the same as the last segment of the subscription pattern.

For example, with the pattern factory/line1/+:

  • A message on factory/line1/oven_temp has lastSegment = "oven_temp".
  • A message on factory/line1/door_open has lastSegment = "door_open".

A topic pattern is said to have a trailing wildcard when its last segment is +. A + in any other position is a middle wildcard and does not affect naming.

Naming for Raw Encodingโ€‹

Topic PatternFriendlyNameResulting Sensor Name
No trailing wildcardSetFriendlyName
No trailing wildcardNot setlastSegment
Middle + onlySetFriendlyName
Middle + onlyNot setlastSegment
Trailing +(rejected)Configuration is rejected at startup. See Wildcard Rules.

Naming for JSON Encodingโ€‹

Topic PatternNumber of FieldsFriendlyNameResulting Sensor Name
No trailing wildcard1SetFriendlyName
No trailing wildcard1Not setlastSegment
No trailing wildcard2 or moreSetlastSegment/FriendlyName
No trailing wildcard2 or moreNot setlastSegment/Path
Trailing +1AnylastSegment (FriendlyName is ignored)
Trailing +2 or moreSetlastSegment/FriendlyName
Trailing +2 or moreNot setlastSegment/Path
info

The MaintainX tag mapping experience lets you rename sensors after the agent creates them. The matrix only applies to the agent's initial naming. For details, see Smart Tag Mapping for OT Data.

Worked Naming Examplesโ€‹

The examples below show the matrix in practice. Each shows the subscription, a sample received topic, and the resulting sensor name(s).

Example 1: Raw, No Wildcard, With FriendlyNameโ€‹

{
"Topic": "plant/boiler/temperature",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Number",
"Unit": "ยฐC",
"FriendlyName": "Boiler Temperature"
}
}
Received TopicSensor Name
plant/boiler/temperatureBoiler Temperature

Example 2: Raw + Auto With Trailing Wildcardโ€‹

{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
Received TopicSensor NameInferred Type
factory/line1/oven_tempoven_tempNumber
factory/line1/door_opendoor_openBoolean

FriendlyName cannot be set here. For more information, see Wildcard Rules.

Example 3: JSON, Multiple Fields, No Wildcardโ€‹

{
"Topic": "plant/line1/sensors",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{ "Path": "temperature", "Type": "Number", "FriendlyName": "Temperature", "Unit": "ยฐC" },
{ "Path": "humidity", "Type": "Number", "FriendlyName": "Humidity", "Unit": "%" }
]
}
}
Received TopicSensor Names
plant/line1/sensorssensors/Temperature, sensors/Humidity

When more than one field is extracted, the last segment is prefixed to keep sensor names unique within the group.

Example 4: JSON, Multiple Fields, Trailing Wildcard, No FriendlyNameโ€‹

{
"Topic": "plant/+/readings",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{ "Path": "temp", "Type": "Number", "Unit": "ยฐC" },
{ "Path": "pressure", "Type": "Number", "Unit": "psi" }
]
}
}
Received TopicSensor Names
plant/line1/readingsreadings/temp, readings/pressure
plant/line2/readingsreadings/temp, readings/pressure

Note that the + here is a middle wildcard, not a trailing one. Without a FriendlyName, each field's Path is used as the suffix.

Wildcard Rulesโ€‹

The On-Premise Agent enforces two rules at startup. The agent will refuse to start if any subscription violates them.

# Is Not Supported for Custom Subscriptionsโ€‹

The multi-level # wildcard is not supported on Custom subscriptions. Use the per-segment + wildcard at each level instead.

Not SupportedUse Instead
factory/#factory/+ (one level) โ€” or list each level explicitly

FriendlyName Is Rejected on Raw Subscriptions Ending With +โ€‹

When a Raw subscription's topic ends with +, the wildcard segment is what makes each matched topic distinct. Setting a static FriendlyName would collapse every matched topic into a single sensor, overwriting one another's data. To prevent this, the agent rejects the configuration at startup with the following error:

'FriendlyName' cannot be set when the topic ends with '+' at /Subscriptions/{N}/Payload (topic: '<your-topic>'). The last topic segment is used as the sensor name to distinguish each matched topic; a static FriendlyName would collapse them all to the same sensor. Remove the 'FriendlyName' field to let the last topic segment be used as the sensor name.

The same restriction does not apply to Json encoding, because the field's Path (or FriendlyName) is appended to the last segment when there are multiple fields, keeping sensor names unique.

Ignored Topicsโ€‹

Use IgnoredTopics at the top level of MqttSubscriptionSettings to filter out topics that would otherwise match a subscription. Patterns use the same + wildcard rules as subscription topics.

{
"MqttSubscriptionSettings": {
"Version": "v1",
"Subscriptions": [
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": { "Encoding": "Raw", "Type": "Auto" }
}
],
"IgnoredTopics": [
"factory/line1/heartbeat",
"factory/line1/diagnostics/+"
]
}
}

A topic that matches any entry in IgnoredTopics is dropped before subscription matching runs.

Full settings.json Exampleโ€‹

The following example combines a Raw + Auto subscription on a wildcard topic, a multi-field Json subscription with dynamic units and a timestamp, and an IgnoredTopics entry:

{
"Integration": {
"AccessToken": "{MaintainX-Connector-Token}"
},
"Mqtt": {
"client": {
"credentials": {
"username": "{MQTT-Broker-Username}",
"password": "{MQTT-Broker-Password}"
},
"tcpServer": {
"server": "{MQTT-Broker-Host}",
"port": 8883
},
"tls": {
"useTls": true
}
}
},
"MqttSubscriptionSettings": {
"Version": "v1",
"Subscriptions": [
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Description": "Per-tag scalar values from line 1",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
},
{
"Topic": "plant/+/readings",
"SubscriptionType": "Custom",
"TimestampField": "ts",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "temperature",
"Type": "Number",
"FriendlyName": "Temperature",
"UnitPath": "units.temperature"
},
{
"Path": "pressure",
"Type": "Number",
"FriendlyName": "Pressure",
"UnitPath": "units.pressure"
},
{
"Path": "online",
"Type": "Auto",
"FriendlyName": "Online"
}
]
}
}
],
"IgnoredTopics": [
"factory/line1/heartbeat"
]
}
}

Troubleshootingโ€‹

The Agent Fails to Start With a Validation Errorโ€‹

The agent validates MqttSubscriptionSettings at startup. The two most common failures are:

  • # wildcard in a Custom subscription
    • Issue: The # wildcard is not supported in Custom subscriptions.
    • Resolution: Replace # with + per level.
  • FriendlyName set on a Raw subscription whose topic ends with +
    • Issue: Raw subscriptions with wildcard topics cannot have a FriendlyName.
    • Resolution: Remove the FriendlyName. For more information, see Wildcard Rules.

The error message in the agent's logs identifies the offending subscription by its index in the Subscriptions array.

No Sensors Are Being Createdโ€‹

Tail the agent logs:

docker logs -f on-premise-agent

Common causes:

  • Topic did not match any subscription
    • Issue: The broker is delivering messages, but no topic pattern matches.
    • Resolution: Check for typos and remember that # is not supported for Custom.
  • No sensor values parsed for topic: ...
    • Issue: The topic matched, but the payload could not be decoded.
    • Resolution: For Json subscriptions, this usually means the JSON is malformed or the configured path does not exist in the payload.
  • Skipping JSON payload for Raw+Auto topic ... (debug-level)
    • Issue: A Raw + Auto subscription received a JSON-shaped payload and skipped it.
    • Resolution: Switch the subscription to Json encoding.

A Sensor's Values Stopped Updating After a Configuration Changeโ€‹

Look for the type-mismatch warning quoted in Data Type Locking. If you see it, the sensor's type was locked at creation and the new payloads are being dropped. Delete the sensor in MaintainX and let the agent recreate it on the next payload.

Resourcesโ€‹