MQTT Explorer provides a flexible decoder system to interpret binary MQTT payloads in various formats. Decoders transform raw binary data into human-readable formats, making it easier to inspect and debug MQTT messages.
Decoder Architecture
The decoder system is built around the MessageDecoder interface, which allows different decoders to be chained and applied to incoming messages:
interface MessageDecoder < T = string > {
formats : T [] // Supported format names
canDecodeTopic ? ( topic : string ) : boolean // Optional topic filter
canDecodeData ? ( data : Base64Message ) : boolean // Optional data filter
decode ( input : Base64Message , format : T | string | undefined ) : DecoderEnvelope
}
Available Decoders
MQTT Explorer includes three built-in decoders:
Sparkplug B Decodes Sparkplug B protocol payloads automatically based on topic patterns
Binary Decoder Interprets raw binary data as primitive types (int8, uint32, float, etc.)
String Decoder Default decoder for UTF-8 string payloads
Base64 Message Foundation class handling binary-to-string conversions
Binary Decoder
The Binary Decoder interprets raw bytes as primitive data types. It supports both single values and arrays.
Format Description Byte Size Example Use Case int8Signed 8-bit integer 1 Temperature sensors (-128 to 127) int16Signed 16-bit integer (LE) 2 Small numeric values int32Signed 32-bit integer (LE) 4 Timestamps, counters int64Signed 64-bit integer (LE) 8 High-precision timestamps uint8Unsigned 8-bit integer 1 Status codes, percentages uint16Unsigned 16-bit integer (LE) 2 Port numbers uint32Unsigned 32-bit integer (LE) 4 Large counters uint64Unsigned 64-bit integer (LE) 8 File sizes float32-bit floating point (LE) 4 Sensor readings double64-bit floating point (LE) 8 High-precision measurements
All multi-byte integers and floats use Little Endian (LE) byte order.
Using the Binary Decoder
Select a topic with binary data
Click on any topic in the tree that contains binary payload data.
Open the format dropdown
In the topic details panel, click the format dropdown button (shows current format like “string”).
Choose a binary format
Select the appropriate format (e.g., float, uint32) from the dropdown menu.
View decoded value
The binary payload will be decoded and displayed as a readable number or array.
Implementation Details
The Binary Decoder reads data using Node.js Buffer methods:
const decodingOption = {
int8: [ Buffer . prototype . readInt8 , 1 ],
int16: [ Buffer . prototype . readInt16LE , 2 ],
int32: [ Buffer . prototype . readInt32LE , 4 ],
int64: [ Buffer . prototype . readBigInt64LE , 8 ],
uint8: [ Buffer . prototype . readUint8 , 1 ],
uint16: [ Buffer . prototype . readUint16LE , 2 ],
uint32: [ Buffer . prototype . readUint32LE , 4 ],
uint64: [ Buffer . prototype . readBigUint64LE , 8 ],
float: [ Buffer . prototype . readFloatLE , 4 ],
double: [ Buffer . prototype . readDoubleLE , 8 ],
} as const
Array Decoding
When a payload contains multiple values, the decoder automatically creates an array:
Example: 4-byte payload as uint16
Example: 8-byte payload as float
// Input: Buffer [0x01, 0x00, 0x02, 0x00]
// Output: [1, 2]
The payload length must be evenly divisible by the format’s byte size. If not, decoding fails with: "Data type does not align with message"
String Decoder
The String Decoder is the default decoder for text-based payloads. It converts Base64-encoded message data to UTF-8 strings.
export const StringDecoder : MessageDecoder = {
formats: [ 'string' ],
decode ( input : Base64Message ) {
return { message: input , decoder: Decoder . NONE }
},
}
MQTT Explorer automatically attempts to parse string content as JSON for pretty-printing:
Valid JSON (auto-formatted)
Plain String (as-is)
Hex View
{
"temperature" : 22.5 ,
"humidity" : 65 ,
"timestamp" : 1638360000
}
Base64Message Class
All decoders work with the Base64Message class, which provides efficient binary-to-string conversion:
class Base64Message {
// Create from buffer
static fromBuffer ( buffer : Buffer ) : Base64Message
// Create from string
static fromString ( str : string ) : Base64Message
// Convert to buffer
toBuffer () : Buffer
// Convert to string
toUnicodeString () : string
// Format with type hint
format ( type : 'string' | 'json' | 'hex' ) : [ string , 'json' | undefined ]
// Convert to hex representation
static toHex ( message : Base64Message ) : string
// Create data URI
static toDataUri ( message : Base64Message , mimeType : string ) : string
}
Example Usage
Creating Messages
Formatting
// From buffer
const msg1 = Base64Message . fromBuffer ( Buffer . from ([ 0x48 , 0x69 ]))
// From string
const msg2 = Base64Message . fromString ( "Hello" )
// To hex
const hex = Base64Message . toHex ( msg1 ) // "0x48 0x69"
Decoder Envelope
Decoders return a DecoderEnvelope that contains the decoded message or error:
interface DecoderEnvelope {
message ?: Base64Message // Decoded message
error ?: string // Error if decoding failed
decoder : Decoder // Which decoder was used
}
enum Decoder {
NONE , // No special decoding
SPARKPLUG , // Sparkplug B decoder
}
Error Handling
When decoding fails, the envelope contains an error message:
// Example: Binary decoder alignment error
{
error : "Data type does not align with message" ,
decoder : Decoder . NONE
}
// Example: Sparkplug decoder failure
{
error : "Failed to decode sparkplugb payload" ,
decoder : Decoder . NONE
}
Custom Decoder Development
To create a custom decoder, implement the MessageDecoder interface:
Define your decoder
export const CustomDecoder : MessageDecoder = {
formats: [ 'custom-format' ],
canDecodeTopic ( topic : string ) {
// Optional: filter by topic pattern
return topic . startsWith ( 'custom/' )
},
decode ( input : Base64Message , format : string ) {
try {
const buffer = input . toBuffer ()
// Your custom decoding logic here
const decoded = yourDecodingFunction ( buffer )
return {
message: Base64Message . fromString ( JSON . stringify ( decoded )),
decoder: Decoder . NONE
}
} catch ( error ) {
return {
error: `Failed to decode: ${ error . message } ` ,
decoder: Decoder . NONE
}
}
}
}
Register the decoder
Add your decoder to the decoders array in app/src/decoders/index.ts: export const decoders = [
SparkplugDecoder ,
CustomDecoder , // Add here
BinaryDecoder ,
StringDecoder
] as const
Test your decoder
Publish test messages to topics matching your pattern and verify decoding works correctly.
Decoders are tried in order. Place more specific decoders (like Sparkplug) before generic ones (like String).
Decoder UI Integration
The decoder system integrates with the UI through the TopicTypeButton component:
// User selects format from dropdown
node . viewModel . decoder = { decoder , format }
// Decoder is applied automatically when rendering
const decoded = decoder . decode ( message . payload , format )
Visual Indicators
The UI shows decoder status:
Format button : Displays current format (e.g., “Sparkplug”, “float”, “string”)
Warning icon : Appears if decoding fails with error tooltip
“Decoded SparkplugB” : Label shown when Sparkplug decoder is active
Advanced: useDecoder Hook
The useDecoder hook provides reactive decoder updates: export function useDecoder (
treeNode : TreeNode < TopicViewModel > | undefined
) : DecoderFunction {
const viewModel = useViewModel ( treeNode )
const [ decoder , setDecoder ] = useState ( viewModel ?. decoder )
useSubscription ( viewModel ?. onDecoderChange , setDecoder )
return useCallback (
message =>
decoder && message . payload
? decoder . decoder . decode ( message . payload , decoder . format )
: { message: message . payload ?? undefined , decoder: Decoder . NONE },
[ decoder ]
)
}
This hook ensures the UI updates when the user changes the decoder format.
Best Practices
Choose the Right Format Match the decoder format to your device’s actual data type to avoid misinterpretation
Handle Errors Gracefully Always return error messages in the DecoderEnvelope rather than throwing exceptions
Optimize for Performance Decoders run on every message update, so keep decoding logic efficient
Test Edge Cases Verify behavior with empty payloads, misaligned data, and malformed content
See Also